November Game Hack
It’s November. Let’s make something fun and stop thinking about Untitled Block Thing for a bit.
Dec 3 update: play on Itch
Wrap Up
Rankings are in and November Man and Possum came 146th out of 554 entries! Pretty decent for a first timer. I am excited to enter again next year and try crack the top 25% next time (I was so close). Overall rankings are shown below, I really failed to adhere to the theme of the Game Jam, ‘cliche’. but everything else was pretty consistent!
November Man also recieved some really nice written reviews. In the order I screenshotted them:
Game Off Day 1
The Game Off 2022 submission window has closed and voting is now open! I’ve checked out a few games already. A lot of Unity based games.
Two games I have really liked so far are Don’t! Spill the Beans and An Original Fairytale.
Don’t! Spill the Beans is a simple but challenging memory / bean tracking game where you have to click on beans and their intended target. The beans come faster and faster and keeping up get really challenging. A few small glitches but a really cool idea.
An Original Fairytale leans into the Game Off theme of ‘cliche’ with a knight on a mission to rescue a princess from a castle, platform jumping through a forest of obstacles. The gameplay is extremely well done - it’s amazing that it was made in a month. The art is wonderful too and the dev has included a bunch of secret hidden rooms that make exploring the 2D world a lot of fun.
Final Stretch
It is the last day of November! I shared the game out to a couple friends and they found more bugs and typos. I fixed most of them and I kept one: the player character moves faster when controlled by WASD to travel diagonally. It’s fixable but I think it is sorta funny. And Minecraft carts do it too, so it’s probably ok.
Other than that, I think the game is good to go!
Birds and Bugs
Some bug squishing today! I fixed some input conflicts that made the player character travel at very high speed. I think I may have figured out the mobile device audio streaming issue - more testing still required.
I also added chickens. They don’t do anything but I like chickens (I have 9 IRL).
The soundtrack is live on Bandcamp, only one of the two tracks is in the game - trying to fade in/out audio files seems like it is too difficult for WebAudio, at least on Safari: a lot of zipper noise and popping and distortion, which is a shame. The second track was only intended to play in the Northern Temple, it has a bit of a darker feel and some robotic sounding drums. The tracks were made on Ableton with soft and hardware synths, my mixing desk is broken at the moment so more soft-synths than usual. The tracks are named for, and inspired by, different dragon-fly type insects that visit my backyard in Summer: Green Darners and Meadowhawks.
Later:
I uploaded and entered the game into the Itch & GitHub ‘Game Off’ game jam. Excited to see if anyone plays it. Many of last year’s Game Off entries are super imaginative and really impressive; this was my first game so I just appreciated being able to work towards a goal. I think I can still make bug fixes and re-upload changes prior to the end date.
I had to create some screenshots for the Itch submission.
Where I started: Where I ended:
Busy busy
It gets busy this time of year! I fixed a couple typos on Thanksgiving but didn’t progress much further. Yesterday I added a new StickySprite class (quicksand puddles etc) to increase the ‘narrative bond’ between November Man and Possum. November Man gets stuck, Possum saves him.
I also worked on getting the soundtrack up and running with mixed results:
- It works perfect on Chrome,
- It doesn’t work at all on iOS safari or mobile chrome.
- It occasionally glitches on Safari.
- I should install Mozilla.
Listen to the soundtrack here!
The only suggested fix I’ve found online is to roll back p5.js to a very old version (I’m on 1.5, need ~0.4). This loses all the WebGL shader stuff. Sorta frustrating, I will continue searching for a solution.
Still working out the final level dialog, attempting to avoid too many words on the screen and overly complex dialog trees… will figure it out today hopefully.
Level Day
A bunch of levels today, focusing on the main story beats of the game. I also cleaned up the level building script to break out some helper methods that kept turning up in level creation.
When I kicked off this project I didn’t know how far I would be able to get in a month but, if I had something half alright, I wanted to enter it in the Github November Game Jam competition. I think I will have enough by the end of November to give it a try! Hooray.
I still have a couple TODOs but i think they are achievable:
- make some of the animations cleaner.
- add the soundtrack (already recorded)
- make randomHuman() have a couple different sprite options to chose from.
- finish up the final level.
- and probably some I can’t think of right now.
art day part 2
Another art day. The shader work got me motivated to put in some new placeholder and final sprites (as good as I’ll get anyway). Some lanky looking humans and a shiny new logo. Worked on some level logic also, need to build out a bunch more levels quickly, can hopefully lean on existing assets and cut / paste / paint fill :-P
Logo is suitably lofi and cheese.
For narrow screens, I made a stacked text logo. It doesnt look as good but it looks better than scaling the art and getting inconsistent pixel sizes. To that point, I sort of fell into a 2:1 pixel density for landscape vs sprite elements, I think it works ok, maybe makes sense in some sorta aesthetic / early video game thinking - landscape textures cover more area so will fill more memory unless they are simple, sprites as small but should be interesting. IDK, making 32x32 pixels look interesting is a challenge - see Personal Space Invaders.
a e s t h e t i c
I was going to draw some human sprites today but instead I thought I would play around with some shaders. The NES colour palette is very bright and cheerful and I really like it, but I wanted to try something a touch gloomier, and also add a bit of nostalgia by attempting to emulate the qualities of an old CRT television. P5 does shaders really nicely using WebGL, but I had to convert all the graphic layers into a single texture to achieve the pass-through effect I wanted. This only really effected the UI layer, so I gave up on trying to find a method to rotate p5.Graphics objects in other p5.Graphics images and drew some new animations for the compass. The LayerHandler now renders all graphics layers into a single texture which is then passed into a shader. The shader applies colour changes (desaturation-y sepia-y), adds some static noise and CRT scanlines, distorts the image to take the shape of a CRT screen, and finally blurs and blooms the texture. Before and after shown below, I may need to more the UI elements in a touch but I think the result looks a bit more cohesive, more lofi.
Over the weekend I also cleaned up the ChasingSprite AI. Implementing the possum exposed a scenario in which the randomWalk and approachTarget functions could occur in the same loop, creating some crazy animation choices and sprite moonwalking. I also cleaned up the dialog renderer to better calculate the size of the dialog background rectangle. small things.
Friends!
I had the idea last night that a companion animal might be a fun mechanic in the game, opening up some new types of encounters and making recurring puzzles easier the second time around. I don’t have a #1 favourite animal but Opossums are def in the top 10. They’re funny looking creatures that do good work in nature - eating ticks and cleaning up after larger carnivores. So now the player character has a companion opossum who joins the PC and helps out by fetching crates. Sorta cute.
I also did a little work on the transitions, cleaning them up and adding an entry animation.
Later: optimising the possum pickup path is something I could spend a couple days on. But I have like a week and a bit left for this project so I won’t:
The possum enters the level and starts grabbing pickups for the player. The logical method (assuming a logical possum) is to grab the closest pickup, then the pickup closest to that one, and so on - a greedy possum algorithm. Implementing this in the game loop was interesting to me - the closest pickup can change as the possum travels along the trajectory between one pickup and the next one.
In sparse-pickup levels, this works really cool and looks sorta organic, you can imagine the possum thinking “this one, then that one, ohh nah, then THAT one..”
In more dense pickup fields, this approach resulted in some crazy oscillations. The possum may be following a trajectory that would intercept with a middle-distance between two locations. At this ‘Lagrangian Pickup Point’ the possum is struck with indecision - which is the closest? that one, no this one, no that one… Greedy Algo in the loop without any persistent memory of a plant doesn’t work out in this application. I tried adding jitter, cause I really wanted the organic / chaotic possum energy, but even with jitter I found oscillations could still occur.
So, I took the boring route. The possum moves through level pickups in a predetermined order, optimisation of the pickup order based on the possum spawn point is done on level load.
One weird annoyance I will figure out tomorrow - while the possum extends the enemy class and uses the same motion->animation mapping, it can sometimes moonwalk to a target location. another //TODO
Tidying Up, Picking Up
Generating all the levels at the start of the game was a dumb idea. While level transition was instant, the amount of graphic elements just hanging out in memory, waiting to be shown, was getting sorta massive for a javascript game. So now each level is generated on transition, after the previous level is removed. This causes a slight drop in FPS for the transition moment, but I can hide this with a static title card or something eventually.
Having a method to generate the current level in-game also simplifies some other issues I had encountered and creates some new problems…
Resizing or reorienting the window required the level to create a new background canvas and reorient all the sprites and dialogues. Now I can just completely regenerate the entire level using the same transition level method.
This provided an interesting ‘infinite money hack’ as while the player’s inventory was persistent, the regenerated level would provide opportunity to repeat trades and/or pickups.
Infinite Money Hacks are sorta annoying, so I implemented Inventory Backup and Inventory Recover methods, meaning any time a level is regenerated, the player will have the same inventory as the first time they entered the level.
I also added a Pickup class which drops a random item into Player inventory when triggered. For the draft levels, pickups created some inconstancies in the plot - why say ‘i don’t have boots’ when you clearly have boots? I added some additional dialog options based upon inventory. This will be something I need to keep track of! For Thievery levels it adds a bit more excitement - the chasing sprites will change behaviour based upon inventory, so picking up a chest with food can anger the spiders. It may be worth exploring randomising the dropped item that results from an inventory-based attack. If you have boots and you don’t want to loose them then you may think twice about picking up a mystery item in a chest that could trigger an attack. I might do that.
Later: did it. also some cute and creepy sprites. I spent more time on the cute one.
Minimum Viable Mechanics
The ‘ChasingSprite’ class received some improvements for animation handling and I created a test spider spritesheet. Creepy. With npc interaction via dialog, trade, and attack, I have enough mechanics to start building out some encounters. The grasslands biome map was getting a bit boring so I drew up an Ice and Desert biome. I started creating a little anti-colonial encounter that I think may be fun. Plan is to continue building levels and encounters until I discover some missing code, then try build it. I have a feeling some of my planned encounters will require a dedicated level logic worker, trying to figure out how it will work - it may be small level-specific objects with a generic ‘update’ interface, TBD.
The player character can now also be controlled via directional and WASD keys when playing on computer.
Later: I am missing pickable items! oops.
Thievery
I spent the weekend messing with art layout and today fixing up some of those scattered ideas. Random layouts get tricky when the game can be either portrait or landscape - borders and path width calculation is working alright now. I also added basic Level transitioning and a second level, a Thievery level with a new ChasingSprite class that will pursue the player character and take certain inventory items until the inventory is depleted. The idle behaviour for this class is a random walk that could use some tuning.
I also implemented a bit of logic in the Input class to attempt to prevent high speed oscillation, caused when calculating atan2(dx, dy) for very small dx, dy values. So now the character doesn’t turn into a ghost when they catch up to the controller position. baby steps.
Random Map Generation
A lot of random() and learning more about pixel art. It’s starting to look like a little world! almost.
Pivot
The nice thing about doing creative things without anyone watching is you can change your mind and not have to explain yourself and say the end result is exactly what you intended.
I had an idea - a scrolling game where a character follows a road, encounters people and tries to help. The character has their own mission and they can’t turn back. So sometimes they can’t help.
After putting in some dummy art and building a late night half-baked scroller method, I don’t like the scroller concept so much. Looking at Open Game Art, there are some really interesting ‘single screen levels’ with different biomes, obstacles, etc. Implementing terrain changes in a continuous scroller is possible, but I wonder if the effort is worth it, considering the extra gameplay time added travelling through transition zones. Having the character move from one level to another via a ‘layer transition’ mechanic allows for more varied game settings and less boring travel time. So I’m gonna try do that. Quests (which I had internally rebranded as “Encounters”) will become Layers. Not a big change, but one I think provides some new exciting possibilities.
Later: I’ve added a new PlayerCharacter class that extends the SpriteCollection. PlayerCharacter includes an inventory and some input control specifics. Control input is slightly different for touch screen vs mouse input now - you can guide the character with the mouse or use an ‘invisible joystick’ on touch screens (invisible cause I haven’t drawn it yet). Player inventory is reflected in the UIElements class, drawn on screen, and is also transferrable via Dialog events. A GameLevelHandler class groups NPCs and Dialogs into levels, I want to add background and foreground elements too. Rebuilding the level when the browser window is resized or rotated is still a bit messy, will work on that first.
A spritesheet slicer method makes importing sheets from LPC much easier, I think probably saves a little on load time also. Still looking ugly but getting closer to a point where it is worth trying to make look pretty. good progress!
Art Day
Felt like switching it up so today I played with Perlin Noise and some art generation. Currently I am not a fan. I might need to switch out my goto color palette but I’m also thinking about ‘hand’ drawing all the components anyway. I found the previous test player character sprite got lost in the background so used the Universal LPC Spritesheet Generator tool to make a little guy.
Backgrounds are sorta underwhelming.
Later that day… The perlin noise art annoyed me. I had a look around Open Game Art and found some inspiration + more test assets. I am keeping a running attribution list here.
I created a UIElements class that will display the game progress tracker, inventory. It currently only displays the compass. I created a scrollTracker class to try keep the sprite center-screen-ish. This one needs a bit more thought - I want to avoid using globals to jump into classes, it gets messy and confusing quick.
Sprite Stuff
A SpriteCollection class allows animations to be chained together. The DialogEvent class now includes event triggers and ids, allowing testing of animation changes in reaction to dialog.
I would like re-implement part of the LayerHandler for some QOL improvements, but I am finding a rhythm with dedicated graphics layers for Dialog, Sprites, background, etc.
A common method that always annoys me when implementing is inbounds checking: is (x,y) inside a rectange (x1,y1,x2,y2). It is simple enough to implement with a bunch of ugly if statements. I tried something a little neater this time, leaning on a p5 builtin function:
function bounded(env, x, y) { // envelope = [x1, y1, x2, y2]
let result = {}
result.horizontal = (x == constrain(x, env[0], env[2]));
result.vertical = (y == constrain(y, env[1], env[3]));
result.onLeft = (x < (env[0] + (env[2] - env[0])/2));
result.onTop = (y < (env[1] + (env[3] - env[1])/2));
result.complete = result.vertical && result.horizontal;
return(result);
}
Dialog Stuff
Conversing with NPCs is an important component of the gameplay concept. I have created Dialog and DialogEvent classes to facilitate. The Dialog class holds an entire conversation, it is implemented as a data-tree structure in DialogEvents are nodes.
DialogEvents can have multiple children, allowing different conversational branches based on the User selected dialog options. There can be multiple speakers and multiple branching sections. Dialog is triggered when the player character gets close to an NPC and the dialog for each sprite is positioned onscreen to indicate who is talking.
Timing the conversation is interesting; the difference between a readable conversation and a conversation that ‘drags’ is very small. Providing more reading time for longer sentences helps.
Creating the dialog tree itself is unavoidably messy, I will probably explore a JSON definition and loader script once I start building multiple dialogs. Currently:
testDialog = new Dialog(G.dims.cy + 20, 50);
testDialog.updateCoords('NPC1', testNPC);
testDialog.addDialogEvent('NPC1', 'What\'s the hurry, buddy?');
testDialog.addDialogEvent('PC', 'I can\'t stop! I have to keep going.');
testDialog.addDialogEvent('NPC1', 'I\'m so hungry! Please, do you have any food?');
let parEvent = testDialog.addDialogEvent('PC', '', ['Here you go.', 'I\'m hungry too.', 'No!']);
let thankyou = testDialog.addChildDialogEvent(parEvent, 'NPC1', 'Thank you!');
testDialog.addChildDialogEvent(thankyou, 'PC', 'You are welcome!');
let sorry = testDialog.addChildDialogEvent(parEvent, 'NPC1', 'That\'s ok. Don\'t worry about me');
testDialog.addChildDialogEvent(sorry, 'PC', 'I wish I had food.');
let selfish = testDialog.addChildDialogEvent(parEvent, 'NPC1', 'Selfish much?');
testDialog.addChildDialogEvent(selfish, 'PC', 'I have a family!');
The Dialog class still needs a few additions - I want a ‘decision event trigger’ to control some game events.
I need to make a proper Sprite class soon, grouping drawables and animations with state changing and some other things. Also, an Item and an InventoryHandler class should happen soon, I think it would be fun to be able to save/share inventories between game sessions too; the endgame inventory becoming the starting inventory of the next game.
I think I am most excited to make a Quest class which will hold Sprites, Dialogs, and Items. I have 6 Quest category ideas:
- Charity
- Trade
- Theft
- Recovery
- Delivery
- Conversation
All sorta inspired by Death Stranding.
Hopefully I can made 2 or 3 unique quests for each category. The game concept has a defined ‘ending’ and a play-through should take no longer than 10 minutes, the ‘re-playability factor’ should be determined by the variety of quests that can be experienced in the game.
Game art direction is still undecided, it’s def going to be basic (achievable, also skill-limited) but the current vibe is beyond minimal.
Animation & Contradictions
I made some dummy assets to start figuring out some sprite animation stuff and realised the concept sorta looks cool in portrait mode… so I undid all the orientation forcing code I created yesterday. Today I have the beginnings of an animated ‘drawable’ using sprite sheets. A lot of time was spent trying to reinstall PixelSmash and get it to export the animation, rather than just the first frame of the animation N times. I gave up and used Pixelmator, will have a look for a new program once I need to make proper assets. Animation class has ‘stop frames’ and ‘move frames’. ‘Stop frames’ allow sprites to naturally assume a resting position when an animation trigger is removed (eg, stop walking). ‘Move frames’ do the opposite - if the current frame of the animation is in a ‘rest state’, don’t allow the sprite to move on screen. It makes walking look a little more natural, but a better sprite sheet would help more too. Happy Friday!
Layers & Transforms
I’m probably not gonna post EVERY day of November but it’s the start of the project and I’m having fun.
I’m using p5.js (surprise!) and the Renderer/graphics tools they provide to draw on the screen. I also hope to generate at least some visual content assets with code (generative, procedural, etc) and p5 is awesome for that. I will def draw some assets too.
A little ‘drawable’ class holds a graphic/canvas and related orientation information. A layer handler makes sure they all get rendered in the right order; birds above trees above dirt… Using layers like this also simplifies some of the rotation / transform stuff I have to do. I want the game to be played in landscape1 and rotating complete rendered layers on mobile2 is easier than tryna draw everything sideways, or trying to work with portrait ratio.
A transform class helps with geometry, it’s only 90deg rotation sometimes so the math is simple. User input is clicks and presses, relationships between ‘input screen space’ and ‘layer space’ hasn’t create any headaches yet.
Something I learned today, p5 push() and pop() pixel operations only seem to work on the default/base-layer canvas, so smooth rotation animations will either have to be animated or drawn on the default canvas, not a major issue.
Something else I learned today, my goal of more but shorter .js files is sorta a pain for HTML rendering. The ‘async’ tag with script is helping, but some squishing and minifying may be required in the future.
At the end of the day I tried make a fun little screen border, the pixel pattern can be a little wonky. Will fix when I get to art.
Architecture
Kicking it off with something extremely booooring. I am thinking about architecture and drawing pictures / flow diagrams. Mobile vs Desktop have different input methods and serving both is always tricky. For this project I want to make it nice and sensible from the start. Rendering has similar challenges, the local vs global coords method I implemented in UBT can be improved upon. I’m also probably going to go over the top with atomic .js scripts, the monolithic ‘functions.js’ approach is lazy first, difficult later.