nullawesome
nullawesome
NullAwesome Development
18 posts
Chronicling the development of a 90s style puzzle platform mobile game
Don't wanna be here? Send us removal request.
nullawesome · 2 years ago
Text
New Dev Tools
Things have gotten complicated. I have enough of a game there to stop faffing about with adding elements higgledy-piggledy to a test level and actually start doing some level design. I want to get this game in front of test players as soon as I can so I can get feedback on, you know, if it's any good, how I can make it better, and so forth; and for that I need real levels even if they're in an early, rough form. The problem I'm confronting now is just how much I suck at level design.
Part of the problem is, that lovely JSON format I devised for level layout makes it difficult to visualize a level. So that brings me to the first new dev tool I'm using.
NA-Builder
NA-Builder is an ongoing effort for a level viewer/editor for NullAwesome. It is written largely in Gambit Scheme, with some bits in C; and structured so that I can start with the simplest bit, a JSON parser, and gradually add components on top of that until I have a full-fledged graphical editor.
But alas, I'm nowhere near building the editor. So instead, I settled for an image generator that uses the JSON parser to read a level file and spits out a map of the level in XPM format. Then I have some Emacs Lisp which allows me to send the current buffer to the map generator and opens the resulting image in a separate buffer -- so at the stroke of a key, I can view the results right within Emacs. This considerably quickens the feedback loop when I'm editing a level by tweaking the JSON directly. It works well enough for now.
NA-Builder is not yet ready for wide release. Maybe someday.
Waydroid
The other dev tool I've been making use of is not of my own authorship, but I got it working well enough for normal use.
See, the problem is, the Android emulators that ship with Android Studio suck. I have added keyboard support to the game so that it is easily playable on PCs, Chromebooks, and other devices. But the Android Studio emulator's keyboard support is... spotty. When you hold down a key, it appears to send key-press/key-release events to the app very rapidly, resulting in herky-jerky character movements and an inability to jump with any precision. But when I tried a commercial PC Android emulator like BlueStacks, everything was hunky-dory.
I noticed that Void Linux had a Waydroid package so I thought I might give it a go. Waydroid is an Android environment in a container that uses the Wayland display protocol to present its user interface. I have not joined many of my Linux-using colleagues in embracing the glorious Wayland future; I'm still on Xorg as my display server. But the Weston compositor has an X11 backend so I start Weston in my X session and have Waydroid display on that. This works out really well, and allows me to test out the game entirely on my PC. When I start Waydroid, an ADB connection is automatically established, and Android Studio picks it up immediately.
0 notes
nullawesome · 5 years ago
Text
Believe It Or Not, I'm Still Making Progress On This Thing
A lot of time has passed, and a fair bit of work has been done. Here are the highlights:
Enemy behaviors are more sophisticated, and more closely resemble what will be in the final game. The enemy now patrols an area when in the IDLE state, and gives chase when ATTACKING.
Line-of-sight detection has been implemented for enemies, allowing them to see you. Whether you are seen by an enemy is established by two criteria: direct line of sight (with no solid tiles intervening), and whether you fall within the frustum of the enemy's vision -- that is, are less than a certain distance away and fall within a certain angle above or below horizontally aligned with the enemy. The frustum may be changed on a per-enemy basis. This adds an element of stealth to the game; since you have no weapons and cannot attack directly, engaging enemies is generally A Bad Idea. You'll have to outwit them and use your expert-level hacking maneuvers to deal with them.
The same line-of-sight code above is used to permit Lorn to hack only targets he has a direct line of sight to. This means that hacking enemies will be fraught with more risk; and you may have to e.g., solve a puzzle before you hack a terminal behind a closed door.
Oh yeah! There are doors in this game. They can be opened and closed by linking them to a HackTarget. When closed, they actually put solid tiles on the map (so that movement and line-of-sight code treats them as solid); when they open, they turn those solid tiles into blank spaces. For obvious reasons, they can only be placed to align with tile boundaries.
There is also a new type of entity called a collectible. Collectibles are things you pick up, like coins in Mario. There are lots of ways you can do this, you can offer rewards for getting a certain amount of them (like 100 coins/rings give you an extra life in Mario/Sonic). You can make them power-ups that either act right away or charge a power meter for some sort of special move or something. The way I chose was inspired by Dangerous Dave. (I will stan for John Romero until I die.) Dangerous Dave had two major kinds of pickups: trivial pickups, in the form of gems, that only increased your score; and essential pickups, in the form of a chalice. There was one per level and it was a prerequisite for clearing the level. Collecting the chalice would open the door leading to the next level. There were also a couple of power-ups.
So the way collectibles work in NullAwesome is there are three kinds: I call them Trinkets, pieces of Intel, and Keys. Trinkets are like the gems in Dangerous Dave, or the strawberries in Celeste. They serve as proof that you were there, so that you can impress your friends and intimidate your rivals with your l33tness. They don't have any impact on gameplay except how you are scored, but you can't 100% the game without collecting all of them. They are shaped like things such as floppy disks (probably filled with garbage files), Nemesis controllers, cassette tapes, etc. I always thought it was funny that there were so many early DOS games that had you collect floppy disks instead of coins or whatever.
Intel is much more central to the game. It represents secret information about ZetaCorp that could incriminate them, so it tends to be put in secure locations. You can find it either lying around on CD-like discs or by hacking and logging onto certain, special terminals. You can exit the level without collecting Intel, but you should look for and collect every piece that you can, because it could affect how things turn out in the end... Intel also gets added to a database you can browse where you can read the pieces you collected.
Finally, Keys are small cards that contain cryptographic information. There could be up to three per level, and a few HackTargets cannot be hacked unless you are holding a Key of the proper color.
I'm not gonna do power-ups because this game is complicated enough and I'm trying to stick to one gimmick at a time.
Oh, and one thing I removed:
I took out a logging statement that was running each and every frame. I was wondering why the game didn't seem to run at top speed. I'm an idiot, and I forgot about that logging statement and let it sit around in the tightest part of the game loop for months.
So yeah, I've had crap piling up in my personal life, and even then I managed to get a fair bit done on this game. Lots more left to go, but you know what? I'm not even going to focus on that. I'm going to savor my small victories. Bit by bit, it looks more and more like a real game.
0 notes
nullawesome · 6 years ago
Text
So, Enemies Are A Thing Now
I have decided to start work on implementing enemies. Enemies are entities representing characters who actively try to harm the player or hinder their progress. I made the decision to devise enemies with relatively complex behavior with a finite state machine wherein each possible enemy behavior is represented with a state. States -- defined in EnemyState.java -- include the following:
INERT -- enemy should not move
IDLE -- enemy should remain stationary or patrol an area
ATTACKING -- enemy detects the player and should attack, either close in or at range
SEARCHING -- enemy has lost the player after detecting them, but it knows the player is around, and should actively search by moving toward the player's last known location, giving up after a certain amount of time is spent in this state
HOMING -- enemy has lost the player, and its search timer has expired. It must attempt to return to wherever its "home base" is and, once home, return to the IDLE state. However, if it sees the player while in this state it may go back to ATTACKING.
HACKED -- electronic enemies such as robots can be hacked in this game, just like everything else with a CPU. Hacking enemies triggers a certain behavior, which can vary but of course invariably results in helping the player. The HACKED state may or may not be indefinite, and may or may not trigger a special control mode to allow further control of the enemy (depending on type of enemy).
This opens up a ginormous problem space. Even simple enemies with a single behavior -- say, Goombas and Koopas from Mario, who only know how to walk left and fall off ledges, or walk left and turn around when they reach a ledge -- must track a lot of state. There are frame counters and timers for their respective animations, variables for whether they detect an obstacle, optionally variables for whether they detect a ledge, etc. Each enemy behavior is backed by its own state machine. So what I'm proposing is actually a hierarchical state machine -- a top-level state machine which controls which of several second-level state machines are active. Yeesh!
Thankfully, it's tricky but not impossible. And implementing the core bits has proven far more pleasant than I'd feared. Again, I am quite surprised at how lucky I get by making good decisions at the beginning -- things like choosing appropriate data structures. The EnemyInfo entity component has a field called script which basically holds an enemy's state graph: the possible states, the possible transitions between those states, and the conditions under which a state transition would be triggered (implemented as Criterions). This forms an entire "script" for how an enemy's behavior evolves throughout its lifetime -- hence the name. The individual behaviors for each state are in the EnemyInfo's stateActions field.
The latest commit includes code which spawns a single enemy -- a robotic drone -- and gives it a very simple state graph transitioning between two states, IDLE and ATTACKING, and a simple "move right" or "move left" behavior attached to each. State transitions are triggered by detecting whether Lorn is in the air or not. This is obviously not final-game material, but it's good enough for testing out how the state machine and transitions work -- and son of a gun, it does work! I didn't even have to spend a whole lot of time and effort debugging it. So this mechanism seems rather promising to continue with. I look forward to devising complex behaviors that make enemies seem sophisticated and make you feel like you're up against forces you really have to outsmart and outmaneuver.
3 notes · View notes
nullawesome · 6 years ago
Text
So, Greeblizers Are A Thing Now
So I got Super Mario Maker 2 recently, and that means it's a perfect time to start talking about one of the linchpins of this whole game development effort -- one which I mentioned before, but said I'd only really work on once the basics are in place.
That's right, folks. It's finally happened. I've finally started implementing greeblization. This is basically the jewel in the NullAwesome crown. It's a big part of why the game exists in the first place. In every game I write, I try to incorporate a central idea or algorithm that does something cool while still serving the purpose of the game -- and for NullAwesome I wanted to build a simple platformer that could take levels that were described in a very simple, succinct format and build them out into something that looked cool and ripe for exploration. The hacking mechanic came later -- basically I thought, I always wanted to make a game about "Hollywood hacking", so I combined it with the platforming idea, loosely following the template established by Watch_Dogs, and I had a starting point.
But greeblization is not a new concept. The word is just a private coinage of mine for something that has existed for quite some time within the gaming world -- much like "jump aftertouch", my term for being able to bend the trajectory of a jump in midair. Games have probably had procedurally generated scenery from simple descriptors -- or even from random numbers -- ever since I was a kid. What really crystallized the greeblizer concept for me, though, was seeing it at work in Super Mario Maker.
Super Mario Maker is basically an editor that lets you create your own Mario levels, as well as a game where you can download and play levels you or others have created. You're given a grid in which to work and can add ground, blocks, pipes, coins, enemies, etc. to your heart's content. Different level elements interact in different ways, and this has opened the door for clever designers to build machines, gadgets, and traps with an almost Minecraft-like sense of endless possibilities. But even in something as simple as placing the ground, the brilliance of Super Mario Maker shows through -- because you can paint with the ground brush all over the place and the game chooses specific ground tiles to make sure that the ground looks consistent with its setting (taken from one of several Mario games). It will even add embellishments to make the scenery more convincing -- for example, adding portholes, pillars, and masts if you paint appropriately-shaped ground in an airship level. Also, the algorithm for choosing ground tiles (in a word: greeblizing) appears to be deterministic, always producing the same painting of ground tiles. In this way, Mario Maker lets even a beginner create levels that look as if they came from Nintendo themselves, without having to know about each individual tile in the tileset.
So this concept -- and the fact that I'm a lazy git who doesn't want to hand-place tiles on a map -- inspired the greeblization engine for NullAwesome. Basically the mechanism consists of an interface, Greeblizer, which contains a single method fillRect, as well as an array of such Greeblizer objects, one for each tile type. A tile type is conceptual: ground, lava, etc., and each type maps to a set of individual tiles all having the same properties but a different appearance. Whenever it paints a rectangle from the JSON level description onto the tile map, the TileMap class selects the currently installed greeblizer corresponding to that particular tile type, which in turn will place the tiles on the map according to some criteria. Currently I have a couple of very basic greeblizers in place, as well as a sexy new tile set -- and I'm glad to say that the results look very promising so far. I mean, look at this radness:
Tumblr media
Don't tell me you don't want to totally play this game that much more now.
Another nice thing about this game that makes developing it really fun is the fact that I made some design choices early on which makes changing things -- even ripping out, rearranging, and putting back together entire chunks of the engine -- relatively easy. I think the decision to go with an entity-component system was a good one, as I can simply add an update agent to do something new with the already-existing entities and components if I want some new piece of functionality. And if I change one agent's behavior, it really doesn't affect all the others. In this case, I had intended to put greeblization in from the very beginning, and I had a fillRect method inside the TileMap itself as basically the stupidest thing that could possibly work. It wasn't hard to extract that functionality into greeblizers, expand upon what each greeblizer did, and then have TileMap dispatch to the appropriate greeblizer in its fillRect method rather than having a complete implementation of that method itself.
This isn't me going "look at me, I'm a super genius programmer". Rather, it comes from being an old, grizzled programmer who's made a lot of stupid mistakes in the past. It is encouraging to know that the painful lessons learned from those mistakes have sharpened my instincts to a point where I can sit down and build out a system that is, from the beginning, reasonably well-structured and easy to maintain and evolve. Because holy smokes, have I still got a long way to go. Game development, like any truly joyous pursuit, is hard -- and I don't want to make it any harder on myself than it has to be.
0 notes
nullawesome · 6 years ago
Text
Let's Talk About Death
The latest code check-in adds a much-needed feature: parallax scrolling backgrounds. These get specified with a new directive backgroundImageName in the JSON file describing the level, which specifies the name of an image in the ContentRepository. This is now a required field.
I'm quite happy with the parallax feature, as it does not seem to cause appreciable slowdown, even on my ancient Samsung Epic (based on the OG Galaxy S).
But what I really want to talk about is what I'll be implementing next. Here things will be taking a dark turn. Lorn has, till now, been pretty happy-go-lucky in his little world, jumping on things and hacking things without a care in the world. Even if he jumps off the edges of the level, he will simply fall forever and you can even still steer him left and right. Well, things are about to change as Lorn gets a new ability: the ability to die. Death in video games is a symbol representing failure, and is in a way essential: is a video game really a video game if there's no way to fail? We're getting into semantics and philosophy here, but it suffices to say that we're most likely to think of an activity as a game if there are victory conditions -- in short, a way to win. And if there's a way to win there is, ipso facto, a way to lose.
Maybe I shouldn't have said death is essential. There are games -- like Tetris -- and even platforming games -- like later entries of the Wario Land series -- that do not implement failure in terms of the death of the main character. But death is the default way to represent failure, and is a time-honored trope of the medium. So it is what I choose here to simplify the design process. Here, then, is a (non-exhaustive) list of things deadly to Lorn:
Bottomless pits
Certain tiles (spikes, lava, etc.)
Traps (that haven't been hacked)
Enemy characters
Enemy robots (that haven't been hacked and turned into allies)
Projectiles from enemy weapons
My plan is to implement a one-hit-kill system. Lorn has but a single life point, and if he takes one hit he dies on the spot. Once he does, the stage will be reinitialized and he will be placed back at the beginning immediately. There is no "life" system, meaning the player can retry the current "exploit" as many times as they need to.
My first task is to implement dying in the general case and tie it to an in-game event (like falling down a pit). Then I can define more ways to have Lorn die as I need them. I'll let you know how that goes.
0 notes
nullawesome · 6 years ago
Text
Living the Dream
When I was a kid, I frickin' loved Super Mario Bros. I loved it so much, I immediately came up with ways to improve it. I decided that one day, I was gonna create a Mario game with, like, all the stuff I wanted to see in a Mario game. Super Mario Bros. was just a computer program, after all, and I had burgeoning programming skills in BASIC and assembly. How hard could it be?! I began making drawings of the things I planned to include -- new power-ups, enemies, and characters. Some of them -- like a new power-up that would turn Mario into "'Lectric Mario", who can shoot sparks that are limited in flight distance compared to fireballs, but travel in a straight line and through solid obstacles -- I still think are cool. Others... not so much. For example there was going to be an enemy called "Mr. Dobalina". Yes, I named him after the title character from that Del Tha Funkee Homosapien song[0]. He was going to be a middle-aged man in a tacky beige suit[1], and he would chase Mario down attempting to sell him insurance. I think he would have worked a bit like the Super Mario World enemy Chargin' Chuck -- quick, aggressive pursuit pattern, takes a few stomps to defeat. Some of them were attempts to address what I felt were shortcomings with the original game. For example, one thing I really hated in Super Mario Bros. was pits, so I was going to give Mario the option to equip items, one of which was a grappling hook, so that if Mario fell into a pit he could perform a "last minute save" by deploying the hook, grappling onto the nearest ledge, and climbing the rope as he would a vine.
It's a dream I never entirely let go of. High school, then college, then life happened, and my Mario game took the shape of one thing or another all the while. Back-burnered but not really abandoned. During this time, I also grew up. I never lost my love for games and gaming, but I got different things out of a game as I grew older. One thing I realized was that a good game is not just a bag of mechanics and challenges. The very best games carefully choose these things and match them to one another. The pits in Super Mario Bros. are an integral part of the experience. Removing them, or even bestowing an optional easy out for them like the grappling hook, changes the game significantly. Changing anything about that game, no matter how small, changes the whole game -- for example, Super Mario Bros. puts the player in a unique relationship with power-ups that its successors never really replicated, wherein the fire flower is a reward for platforming skill that allows you to cheese through some of the more annoying obstacles like Hammer Bros. and even Bowser. But in order to keep it you had to remain unscathed for as long as you kept playing, else you would either die or become small and have to find the flower again. Bros. 3 and World, by contrast, almost always let you start a level with the power-up you needed to hand, either through the item menu or by visiting a previous level, topping up on power-ups, and exiting early. So simply by changing the available power-ups and how they are accessible, successors to Super Mario Bros. become fundamentally, substantively different in character.
The cool thing about all of them is not necessarily the power-ups, the levels, or the enemies. Those things are cool, but they're not the essential thing that makes these games awesome. The essence of Mario is in the adventure Mario embarks on, and the precision platforming necessary to succeed in that adventure -- negotiating obstacles and enemies quickly and almost pixel-accurately, with a simple but deep moveset.
In thinking about this I realized that the Mario game I dreamed of writing as a kid is coming to fruition now -- but it's not a Mario game at all. (That would create copyright problems, something 12-year-old me couldn't appreciate.) It's called NullAwesome and it stars a little rad green dude named Lorn Zender. But the way Lorn jumps -- that's a Mario jump. It even has jump aftertouch that feels about right. The way he moves, the level layout (such as it is so far) and how the elevators move -- those are all Mario-influenced elements. There are other inspirations as well, most notably Dangerous Dave -- written by a man who also just wanted to make a Mario game, John Romero[2] -- and OddWorld, and of course Watch_Dogs for the concept.
But yeah, this is basically the realization of a childhood dream for me, albeit taking a form I could never have anticipated. I'm pretty excited nevertheless. I just hope I can do kid-me proud.
I'm currently in the process of linking "things" together, so that hack targets can propagate state changes to objects they're linked to. This allows you to manipulate elevators and other moving obstacles by hacking their linked nodes. I'll check it in once it's in a working state. Stay tuned.
[0] And Del himself got the name from the late Peter Tork on the Monkees track "Zilch", in which all four Monkees rhythmically repeat random spoken phrases they heard somewhere before. Somewhere, the real Mr. Bob Dobalina, perhaps long dead by now, might've lamented his dubious immortalization.
[1] I thought it hilarious that in an era where video game enemies were mainly soldiers, thugs, mutants, or monsters, Bowser would recruit an ordinary, lame guy in a suit to go after Mario. My sense of humor at age 12 was... weird.
[2] John Romero and John Carmack actually did successfully begin a port of Super Mario Bros. 3 for the PC -- only to be turned down by Nintendo. They used the engine for the unfinished game to create the critically acclaimed Commander Keen series and bootstrapped Id Software in the process.
1 note · View note
nullawesome · 6 years ago
Text
So, About Those Refactors
As of January 28, 2019, NullAwesome now requires JDK version 1.8 or later to compile. OpenJDK should be fine; if you have the latest version of Android Studio, you should be okay.
The reason for this is because it turns out that the Android development tool suite does support Java lambdas. So I endeavored to replace use of anonymous inner classes with lambdas wherever possible. I really like how lambdas are implemented in Java. Not because they're ideal -- far from it! "Ideal" would resemble Scheme, Haskell, ML, etc. (Can you tell I'm a total Schemehead?) Rather, they were designed in a way that "plays nice" with Java's existing type system and ecosystem. In Java, a lambda is type-equivalent to any interface with a single method that accepts parameters of the same types the lambda accepts, and returns the same type the lambda returns. These are called "functional interfaces" in Java. Which means that lambda expressions are effectively equivalent to anonymous inner classes, but far more convenient to use. It's a nice feature -- one that should have been in the language since version 1.0 or so, but it's here now so may as well make use of it.
So that's one refactor. The other refactor... it concerns how agents work. See, whenever an UpdateAgent or RenderAgent would run during the game loop, usually it would loop over the entire list of active entities, looking for entities with a particular component type, and updating or rendering those. This was a suboptimal implementation but it was easy to devise and quick to write -- but it may have been a source of barely perceptible slowdown.
So now agents maintain an internal list of entities they're interested in updating. This list is implemented in the class RelevantEntitiesHolder. But REH is more than a list; it also registers callbacks within EntityRepository, notifying the REH whenever a component gets added to an entity, or an entity gets deleted, and allowing the REH to add the entity to its list or not. This removes the need to loop over the entire list of entities for each agent, and also removes the check during the agent's update loop for whether the entity has the proper component type: all entities with the proper component type are automatically added to the REH when they are constructed, and removed from the REH when they are deleted. The main drawback is an unstated requirement that the add callback registered with the EntityRepository must be idempotent, that is, the results must be the same no matter how many consecutive times you call the callback. This is because the add callback may be called more than once for each entity; in fact it will be called each time a new component is added. For REH, this is not a problem.
I managed to scavenge an old Android 4.x cellphone to test on; on that phone, NullAwesome is already looking a bit smoother with these changes in place. The game is even perfectly playable on my really old, Samsung Epic phone from 2010! I probably could have gotten away without making these changes in practice, what with it being a 2D game and all, but I'll take any performance improvements I can get.
0 notes
nullawesome · 6 years ago
Text
A Little About Altea
Hey, folks. Got some major refactors on the way which I hope will help performance a little. More on those later.
In the meantime (in between time?), I'd like to tell you about the world where NullAwesome is set.
Around a small but brilliant star in a galaxy far from us orbits a planet called Altea, fourth out from its sun. Altea is a world much like our own, populated by a variety of plant and animal life -- most notably a race of diverse humanoids called simply Alteans. But it also has some unusual properties, one of which being that its crust has rich deposits of a mineral called altinium. To the Alteans, altinium is more valuable than gold -- and far more useful, for it can be used as a clean and convenient energy source among other things.
Millions of years ago, a calamity struck Altea in the form of a meteorite impact that sent enormous chunks of altinium-rich rock into the air. These chunks of rock didn't fall, but remained floating, suspended in midair; eventually, over time, they coalesced into giant floating islands, encircling and slowly orbiting the globe under the tidal influence of Altea's moons[0]. Today, Alteans call the floating islands "The World Above" and the cold, relatively barren planet surface "The World Below".
The life that survived on the floating islands of the World Above thrived and blossomed into thousands of distinct ecosystems, eventually giving rise to modern Alteans as well as various other plants and animals. However, because the surface of the planet was overshadowed by these islands, it got far less sunlight and became colder, and the life that remained there found it far more difficult to survive. Only a relatively narrow band around the equator is habitable by any but the hardiest of lifeforms. Recently -- in the past 150 years or so -- mining operations on a few of the islands of the World Above succeeded in extracting most of those islands' altinium, causing the islands to slowly descend. Stranded, the miners eked out an existence in the World Below's habitable zone, and established settlements on the surface. Over time, the miners and their descendants found that the surface was rich in altinium -- far, far more than they could extract from the floating islands -- as well as ores for various metals. These resources allowed the settlers on the World Below to develop sophisticated technology very quicky, and eventually powerful technological corporate empires were built along with cities which housed the settlers' descendants and protected them from the harsh environment. These city-states were nominally run on democratic principles, but usually, the conglomerates which formed out of the original mining companies exerted power over the city government with relatively few restraints. The hardy inhabitants of the World Below came to be called "Downers", while the people who lived in the World Above were called "Uppers".
Today, there is still little contact between the World Above and the World Below. Some of the bigger nations in the World Above have established diplomatic and trade relations with a few of the more powerful city-states of the World Below, but travel between the Worlds is rare -- aside from the giant airships that carry raw materials and technological products from Below to Above, and agricultural products, textiles, and artisanal goods from Above to Below.
As for Alteans themselves -- they're kind of like us, but with a few differences. Compared to humans, Alteans:
are much shorter -- 150 cm is a typical height for a tall Altean male.
have proportionately larger heads, eyes, and ears.
usually do not have hair from the neck down.
have a much greater variety of skin, eye, and hair colors -- and skin color is not nearly as strongly correlated with ethnicity as it is for humans.
are about the same in terms of intelligence.
are about the same in terms of absolute physical strength and durability, despite their smaller size.
are much quicker and more agile.
mature at about the same rate as we do but have longer life spans; 100 Altean years (where a year is 384 days) is common.
NullAwesome takes place in the World Below, in a city called Onyxopolis, which was built to support the workers of the Alber W. Zeta Mining Company. Today, that company -- now a high-tech firm called ZetaCorp -- effectively rules the city, and by and large, the people see it and its current CEO, Kadmus Zeta, as their benevolent benefactors. Of course, not everyone in Onyxopolis takes such a kind view toward their corporate overlords -- which is where Lorn and his girlfriend, a young indie reporter named Kira Lee, come in. While investigating corruption in ZetaCorp's higher ranks for her zine, Kira mysteriously vanished -- so Lorn, and you, must rescue her!
So that's a brief primer on Altea and, more specifically, the World Below. I really do owe my friend Jay Osborne a profound debt for the concept, as all his stories are set on an Earth-like planet called Reona, and that more than anything else provided the impetus to create a world of my own for my games.
Oh yes. There'll be more than one.
[0] Altea has two moons: a small one called "Traveller", and a big one called "The World Beyond". They have distinct orbits and their gravitational pull influences the movement of Altea's seas and floating islands.
0 notes
nullawesome · 7 years ago
Text
Holy Crap! I Forgot To Put In Collision Detection!
Sorry for the delay, folks. Yeah, I really wasn't going to cancel development of NullAwesome. That was an April Fool's Joke.
Anyhoo, I added elevators to the game to give Lorn something to hack. I was all set to work out a way to make Lorn stick to the elevator when he lands on it, when I realized, I didn't even have proper collision detection in the game! Sure, Lorn -- and everything else with a WorldPhysics component -- can collide with the ground and things will happen based on that. But what happens when things with WorldPhysics -- like Lorn, elevators, enemies, etc. -- collide with each other?
There are lots of ways to check for collision. Back in the day, your hardware might have some sort of support -- perhaps a flag being set or an interrupt being raised -- whenever two sprites touched one another. (Then again, it might not.) Some systems -- like the Commodore 64 -- even let you read out information about which two (or more) sprites collided.
But it's the 2010s and we're drawing everything in software (perhaps with GPU assistance), and we don't have special hardware support for sprites. So we have to check for collisions in software too. There are many ways of doing this. A naïve implementation would check every collidable object against every other collidable object. This is O(n^2) in the number of objects, so it's fine when that number is small but can get computationally costly when the number gets big. Old NES games, for instance, only track the objects which are actually on-screen, and only support a fixed number of objects. Five to seven was a common number; M.C. Kids supported as many as sixteen. Objects that were off the screen would, at most, be tracked by a single bit indicating whether that object should be respawned when it comes back on the screen. This is why things like enemies "reset" when they were scrolled off and back on the screen. Some games (like Super Mario Bros. 2 and Mega Man) lacked this bit entirely. If you killed an enemy and scrolled the screen a ways, then went back, the enemy would reappear.
It's possible to check all, or most, of such a small number of objects for collisions against each other without appreciable slowdown. It may have been difficult in the NES days, so objects would be checked for collision only against objects of certain types. More sophisticated collision detection schemes may involve checking only objects that are "near" each other by partitioning the game space with a structure such as a quadtree, octree, kd-tree, or even a simple hash table where each bucket represents a chunk of space around certain coordinates. Each object would only be checked for collision against objects within its own chunk, or neighboring chunks.
Back in the day, I worked on a game called Xhedgehog (a Sonic clone). While writing that game, I wrote the naïvest possible collision detection implementation, checking each object against each other object. I was able to instantiate up to about 200 objects without appreciable slowdown on the Pentium-based hardware of the day. I think I tested it by spawning item boxes out the wazoo and then having Sonic hit them all.
So with that in mind, I feel pretty justified in choosing a rather unsophisticated implementation for doing collision detection in NullAwesome, which will also likely peak out at a few hundred active entities per level, tops. Here's how it works. A CollisionUpdateAgent processes all collisions in the game and runs after PhysicsUpdateAgent. This class also defines two interfaces: CollisionUpdateAgent.Collider and CollisionUpdateAgent.Criterion.
A Criterion is actually more like a function of type int -> boolean, but Java not having lambdas until 1.8 makes this difficult to implement under Android. Yes, I know, Kotlin. But I started this project in Java, before Kotlin became a first-class language for Android development, and I'm not going to change now. The WorldPhysics component contains an instance variable collisionCriterion which implements this interface, which exposes a single test method. If we are interested in collision with the entity given by the parameter eid passed to test, test returns true. In this way, each entity can register what entities it is interested in colliding with -- or more importantly, the collision system can skip over testing for collision with entities this entity is not interested in colliding with.
The Collider interface is like a function of type (int, int) -> void. It exposes a single method collide, which takes two eids. The WorldPhysics component contains an instance variable collider which, when called, passes the owner of this WorldPhysics as the first eid and the entity it collided with as the second.
So collision detection only works on entities which have a WorldPhysics. When the CollisionUpdateAgent runs, for each entity A having a WorldPhysics, it loops through the entity table once, and for each entity B that also has a WorldPhysics and is not equal to A, does the following:
calls wpA.collisionCriterion.test(B) where wpA is A's WorldPhysics
if that returns true, it checks the hitboxes of A and B for intersection. The hitbox of an entity is given by wpE.hitbox offset by smE.position, where wpE is the entity's WorldPhysics and smE is its SpriteMovement.
if the hitboxes intersect, it calls wpA.collider.collide(eidA, eidB). The collider than performs whatever state changes are necessary for this particular collision.
Note that each entity has at most one collider and one collision criterion; if an entity wishes to collide with multiple types of entities, with a different action for each, its criterion must return true for all of them, and its collider must then test the collided-with entity again to see what kind it is. This is a very simple, dumb implementation and there may certainly be room for improvement in the future. I decided that this is a good starting point, and if performance becomes an issue we can optimize later.
I tested it out by having the game draw a red square on the screen any time Lorn collided with an elevator. It seems to work, and there's no or negligible slowdown, at least with the few objects I'm testing so far. The story may change when we have hundreds of objects on screen.
0 notes
nullawesome · 7 years ago
Text
NullAwesome Development Suspended Until Further Notice
Hi all. Sorry I don't have much in the way of updates for you. I'm writing to let you know that I'm suspending development on NullAwesome indefinitely. The reason is because I have a much better game idea in the works -- one that I hope will blow the doors off not only the gaming industry, but civilization itself. I call it... Cranberry Assimilator.
Cranberry Assimilator is a stacking puzzle game in which you play as Cranberry-chan, a typical Japanese high school girl based on a completely original design. The object is to win the heart of your senpai by defeating a succession of increasingly difficult rivals by stacking "berries" on the game grid. Any contiguous chain of berries going any combination of up, down, right, left, but not diagonally, connecting opposite sides of the board, will be removed and add points to your score (longer chains = more points). It will also send "poison berries" to your opponent's field. The opponent will not be able to clear colored berries until there are five or fewer poison berries remaining (less than it takes to make a complete chain). Clearing poison berries also does not yield points or attack the opponent. The first to reach a threshold score wins, and the loser will die a horrible, agonizing, gruesome, bloody death. Because this is supposed to be a realistic game about the deadly psychology of obsession.
Currently I have the following rivals planned:
Bestia Frendd -- the long-time friend of Senpai with a secret crush
Vollerie McSports -- the school's volleyball team captain
Sugar Cakes -- the cooking club's leader
Eartha Greenthumb -- the gardening club's leader
Melody Pianissima -- virtuoso member of the school's orchestra
Umbra DeSpell -- goth chick, occult club leader
Gaylord McGee -- the token gay male character
Bellatrix Mafiosa -- all-girl gang leader with a violent streak
Perfecta Ultimatus -- student council president and scion of a wealthy family
Of course, I haven't started on the first rival. Coming up with rival scenarios, and programming them, is incredibly difficult. I would have to add so many IF statements to my code that it'd slow the whole thing down to about three frames per second. While I figure that bit out, I've got the basics of the puzzle grid together (admittedly at about six frames per second), and I've been adding IMPORTANT GAME MECHANICS such as:
the ability to change Cranberry-chan's hairstyle
various easter eggs and meme modes
equipping different stockings on Cranberry-chan for different effects
unlockable backstory segments about Cranberry and Perfecta's families
fixing bugs, like Cranberry's Senpai automatically dying whenever you win a match, or the game board glitching out whenever you try to swap two berries to make a move
Don't ask me when the game, or even the first rival, will be done. Just keep giving me money on Patreon, and watch me stream myself playing Overwatch poorly and swearing into the mic.
Oh yes, and happy April Fool's Day!
1 note · View note
nullawesome · 7 years ago
Text
So Hack Targets Are Kind Of A Thing
Hack targets are just what it says on the tin: they are things that can be hacked. Any entity can have a HackTarget component on it; and when you press the Hack button, red corners will appear around the entity marking an area you can touch to hack that particular thing. What happens when you do hack it -- well, there's still a lot to be implemented there, but there is currently a flag that changes and makes hacked items not hackable anymore.
For any game of this type, you have to sit down and ask yourself -- what is the spine of this game? There's one activity, one goal, that serves as the core of what the game is really all about. And then you build everything else in the game to serve that one thing. For example, Mega Man should have been called Jump'n'Shoot Man because that's what you do: you jump, and you shoot things. And everything about the game, from level layout to enemy placement, to the different kinds of weapons you can get, really reinforces the skills of jumping and shooting.
So early on I decided that this game was going to be about hacking. Originally I was gonna give Lorn a weapon of some sort, like a gun or something, that he could use in emergencies. And maybe it had really limited ammo or only did stun damage as opposed to killing dudes. But eventually I was like nah. I cut out that concept entirely. Move, jump, hack. Those are your only verbs in this game. If Lorn is going to have a weapon, he needs to hack something in order to turn it into a weapon.
See, that's the thing that really gets me about the inspiration for this game, Watch Underscore Dogs. That game had a hacking mechanic in it, but at its core it was another drivey-shooty Grand Theft Auto clone. And most of the game's goals were about "oh, you have to go here and kill or incapacitate this dude". I mean the big sell was the hacking stuff, but the hacking stuff was added as kind of a sidecar to all the other content in the game, which focused on GTA-style gameplay. And there's not really anything wrong with GTA-style gameplay per se, but I'm trying to make a really small game centered around the same theme of hacking stuff. So from my perspective, in keeping with the game's essentialist aesthetic, it makes sense to go the other direction and make hacking the only special thing you can do in the game.
Which means, of course, that in order to make progress in developing the game, I have to move the needle on implementing hacking mechanics. It's a bit trickier than I anticipated from the outset, and I've been busy with other stuff as well. But I'm happy to be finally moving the needle on this.
Here's a typical hack target:
Tumblr media
I call this thing a Terminal Node. It's basically a freestanding computer system that's connected to the ZetaCorp network. Hacking one of these can either give you Intel (the game's primary collectible), give you a Crypto Key (required to hack some things in the game), or control other objects in the game world like an elevator or door. The HackTarget can be applied to other objects too, such as enemy drones (which, when hacked, will turn to your side), but this is kind of the basic thing you can hack. One is bound to turn up very early in the first level, to show you how the mechanic works and a little bit of what it can do.
I really like the look of this thing. The dark blue and purple -- those are the ZetaCorp colors. Remember when I was talking about Bubsy, how you couldn't read anything in that game and it was just a confusing mess? Yeah, I'm trying to avoid that and this color scheme is part of that. You see those colors on something, you know it will be bad. The red text on the monitor will also turn green when you've successfully hacked it, and there will be an indicator of that sort on other hackable items as well. It also really reminds me of like an SGI workstation from the 90s. Back in the 90s we really did think that RISC architecture was going to change everything, so this is what all computers were going to look like in the future, circa 1996 or so.
Also, if you've ever played the game Neon Chrome, you know the sort of aesthetic vibe I'm going for, the sort of blue and purple, neon, cyberpunk stuff. Go play Neon Chrome, by the way. It's excellent.
0 notes
nullawesome · 8 years ago
Text
So I Found The Major Rendering Bottlenecks
Recent builds of NullAwesome should be much smoother. I found two major bottlenecks that were hampering rendering speed and creating a... less than smooth experience. The first was, on each rendering cycle I was creating a new Canvas for the back-buffer bitmap to perform the draw calls for the background and sprites with. I changed that to create a single Canvas in the DrawAgent constructor, which the DrawAgent then hangs onto for its entire lifetime. So that created a noticeable improvement.
The other thing I did was to update the EntityRepository to reduce the number of entity IDs actually looped over when an update agent processes entities. The previous code had an update agent loop over every possible entity ID, that is from 0 to MAX_ENTITIES (currently set to 8,192). The current code keeps track of the highest entity ID seen so far and only loops from 0 to that high water mark. Also, any time you delete an entity, the high water mark is recomputed. Given that I anticipate a relatively small number of entities to appear per level (into the hundreds, tops), that's much less processing the update agents have to do per frame. The improvement from this change may not be visible on recent devices, but it sure is noticeable on the six-year-old Samsung Galaxy phone I have kicking around, and was testing with.
I vastly underestimated the amount of processing there is in looping over 8,192 entity IDs, checking to see if each was "active", and if so running a procedure on it. I thought that the looping over inactive EIDs would disappear into roundoff error. I guess I was wrong.
I suspect that there is much more room for optimization here, but I will have to do more in-depth profiling to do it.
Special thanks to Jay Osborne for confirming the performance improvements. He's been play-testing some of my early builds, and has been an invaluable resource.
0 notes
nullawesome · 8 years ago
Text
Game Design Lessons Learned -- From A Bobcat In A T-Shirt
Ever since Mario exploded into a media phenomenon in the late 1980s to early 1990s, game companies have been trying to capitalize on that success by introducing their own mascots starring in their own video games, starting with Sega's Sonic the Hedgehog from 1991. Well, it didn't start with that game, but that was the game that really kicked the mascot craze into high gear and produced a character which could (at the time) challenge the dominance Nintendo and Mario enjoyed.
But the thing is, a mascot really only works if it stands for something. Mario not only represented Nintendo as a company, his games stood for the values Nintendo was widely considered to stand for: fun for the whole family, games for all ages and skill levels. Even Sonic, despite mainly being a big ball of attitude, represented the marketing angle that Sega was taking to compete with Nintendo, producing more complex and more importantly, edgier games that appealed to a late-tween to teen demographic. But really, at the end of the day what does Mr. Nutz represent, besides shameless me-too-ism?
Okay, I'm getting off track. The point is, after Mario and Sonic blew up among the public, every game company felt the need to develop a mascot platformer of some sort. Usually the mascot was a Sonic ripoff -- some sort of cartoon animal with "attitude". These ranged from the interesting (Rocket Knight Adventures), to the merely bland (the aforementioned Mr. Nutz), to the downright awful such as today's subject: Bubsy the freaking Bobcat.
The truly flabbergasting thing about Bubsy is how bad his games are. Every single one of them -- and they just get worse and worse, culminating in the God-awful, franchise-ending cascade of fail that is Bubsy 3D. But they're competently coded; they don't glitch out like, say, Cheetahmen. They're just -- nearly any time the designers had an opportunity to make a game design decision, they made a bad one. Strangely enough, that makes Bubsy a useful learning tool for aspirant game designers like me.
Everybody should play Bubsy to get a sense of what these games do that's so fundamentally wrong. We take good game design principles so for granted that sometimes it takes a bad game -- like Bubsy -- to get us to notice them because they're missing. Just go ahead and track down the SNES version or -- I hear the first couple of games are out on Steam now, so I dunno, you can go get 'em from there. Just play through the first few levels of the first game, Bubsy in Claws Encounters of the Furred Kind. It won't take you long to find the fail.
For one thing, the game messes with your ability to anticipate danger. It just seems like everything in this game is designed to pop out of nowhere and kill you. Other platform games -- such as Mario and Sonic -- have level layouts that let you spot and react to an enemy or obstacle before it poses a real threat. Not Bubsy. For one thing, the first Goomba in Super Mario Bros. is two full screens away, giving you plenty of time to anticipate his arrival. The same goes with the first Motobug in Sonic. Note also how it takes a while for Sonic to build up to his full speed over level ground, so even if you remain at ground level, you still have plenty of time to anticipate the Motobug and jump on or over him. And the game distracts you with a 10-ring item box up on a ledge, so if you go get that, the first Motobug will miss you entirely (but you will still see him so you have some idea of the peril that lies ahead).
Bubsy? The first enemies (plural!) are in the very next screen over from your starting point. Notice how Bubsy is poised when the level starts, like a runner on his mark, ready to book it. The game encourages you to go fast, in a manner like Sonic. Bubsy himself has a steep acceleration curve. He goes from zero to booking it in like two seconds. With the huge sprites in this game, that means he's probably going to run smack into those enemies before you have time to react to them. The game encourages you to go fast and then immediately punishes you for doing so, pointing and going "Ha ha!" like Nelson Muntz. You simply cannot go full speed in this game without paying a heavy cost. You don't even need to touch enemies in order for them to kill you. One very common enemy can literally kill you by sneezing on you. Another problem is that the camera waits for Bubsy to reach the top before scrolling up, or the bottom before scrolling down. This makes it hard for you to see what you will land on when jumping, bouncing, or falling -- and you could land on an enemy (insta-death) or water (also insta-death) without realizing it. If there's a ledge above you, you may not know what's on there until you land on it. Could be nothing. Could be an enemy, or a crate full of thumbtacks. Normally these games are designed to encourage exploration, so that if there's a platform up high somewhere, the player will want to climb up there. But in Bubsy, the levels are laid out in such a way that any route the player may wish to take is fraught with nasty surprises -- and the player won't know what's there until it's too late to respond. That's bad design.
It gets worse. There are secret tunnels throughout the level, and some of them go straight into water without you having any way of knowing -- or doing anything about it once you've committed to taking that route. You just gotta memorize which ones go where -- which is hard because all the entrances look alike -- except the ones that have manhole covers which can kill you.
Another way this game fools with your ability to anticipate danger is there are innocuous-looking things which are actually quite dangerous. For example the manhole covers I just mentioned, which flip around to periodically open and close certain pipeways, but can also kill you if they hit you. There are also the famous gumball machines which shoot tiny, barely visible gumball projectiles that kill you in one hit. They look a fair bit like the halfway marker in Sonic, or at least like nothing that will kill you. But they will. So you have to watch yourself when you encounter anything that looks unfamiliar in this game. In one part of the game, all of a sudden and for no reason, there are yellow cars which endlessly respawn. They're large and they move pretty fast, so of course you have to have Jedi reflexes to anticipate them but they can be killed. Then there are the red convertibles that look almost exactly like the yellow cars -- except when you follow your instincts and jump on them the way you did the yellow cars, you become trapped inside and die.
Did I mention you die in one hit? Because -- yeah, you die in one hit. Touching, or even sufficiently approaching, an enemy kills you (unless you land on top of it with sufficient accuracy), a gumball from a killer gumball machine kills you, water kills you. Seriously, water kills you. Why? Because cats don't like water, HURR HURR. Never mind that some breeds of housecat love water; and bobcats are excellent swimmers. Nope, they just decided to make water deadly for an easy cartoon gag. Not even Sonic did this; Sonic sinks like a stone because of a false belief that hedgehogs can't swim, but water isn't immediately deadly to him and the underwater parts of Sonic stages have interesting (if challenging and frustrating!) mechanics. But in Bubsy water is deadly. Except when it isn't. Falling from a sufficiently great height kills you. Seriously? Falling damage in a platformer? I swear I've died for reasons I couldn't guess, like falling off a rollercoaster in the fourth level. After restarting on a fresh life, I was unable to replicate this death. Strange. The controls are some of the worst.
The controls are simply awful. Basically, there's no implementation of momentum that maps to anything in the physical world. Bubsy accelerates to full speed from a standing stop before he travels one screenful; and he screeches to an immediate halt once you let go of the control pad. Oh, except if he's on a grade in which case he will walk, automatically, at a pretty good clip, down towards the bottom of the hill. Sometimes, when running up a grade, instead of slowing down the way Mario or Sonic might, he will speed up. When he jumps, it's even worse because of the wonky mechanics around what I call jump aftertouch. Jump aftertouch is the ability -- pioneered in Super Mario Bros. -- to steer the character's trajectory after they jump. Bubsy has very unresponsive jump aftertouch -- even worse than Sonic. Unless you jump from a standstill, you are pretty much unable to steer your midair trajectory. And even you think you are jumping from a standstill, you may not be because of the rule about automatically running down slopes. If your takeoff point has the slightest grade to it, you may drastically overshoot -- or undershoot -- your jump, sending you careening into an enemy, or water, or a red convertible, or whatever other hazard lurks just beyond the screen.
All of this is invalidated, of course, if you use the glide. Why does Bubsy have a glide move? It doesn't even appreciably slow his descent. If used it prevents fall damage, it allows much better control over Bubsy's jump arc, and it increases the size of his hitbox for collision detection purposes when he's killing an enemy. (His hitbox is always huge when it comes time to determine if Bubsy is killed...) It's more of a "fix what's broken about jumping in this game" button. Why not make jumping not broken to begin with and give Bubsy some other move? Maybe a dashing scratch on the ground and a homing pounce from the air? Super Mario 3D World with its Cat Mario power-up gives you better cartoony cat-themed gameplay than does any Bubsy game.
And all of these flaws -- poor level layout, the inability to make sense of just what is a hazard in Bubsy's world, the wonky camera, the janky controls, the fact that everything freakin' kills you in one hit -- would be bad enough on their own. Separately they would have made the Bubsy series a mildly annoying, but forgettable artifact of 90s mascot mania. The seething hate for Bubsy comes from the fact that they all occur together and reinforce each other, creating a stacking effect of frustration.
I won't even get into Bubsy 3D. Not only because it brings terrible to new heights that I don't wish to contemplate, but because NullAwesome is a 2D platformer and I want to focus on aspects of 2D gaming that Bubsy is a negative example of. This isn't really a rant about how terrible a game is -- angrily nerding out about video games is plentiful elsewhere -- it's a dissection of why that game is terrible, so that lessons can be learned about how to make my own game good. Or at least not sucky.
Lesson 1: The player should be able to see well ahead of them. Turns out, I really don't know that much about how cameras work. Careful study of Bubsy's terrible camera -- and the really good camera dynamics from Super Mario Bros. -- convinced me that I should not let Lorn get further than halfway across the screen -- in either direction depending on which way he's headed. The way the camera works in Mario is really quite interesting. The screen starts scrolling when Mario is about 1/3 away from the left edge -- but at a slower pace than Mario's walking speed, allowing Mario to continue advancing across the screen until he reaches the halfway mark, at which point the scrolling matches his speed and he remains horizontally in the middle of the screen.
So a good camera system that doesn't let the camera lag behind the player character and prevent the player from seeing what's ahead is paramount.
Lesson 2: Hazardous things should look hazardous. Why does water kill you? They had lots of options: lava, spikes, poison, bottomless pits, nuclear waste, electricty -- and they chose water?! This lesson is a reminder to myself to carefully choose tiles for environmental hazards that really communicate the sense of "hey, you really shouldn't touch that" to the player. They should stand out and be sharp and pointy, or glow menacingly, or sizzle, or crackle with electricity. The same goes for the enemies: I want to avoid putting in any innocuous-looking, but quite deadly, gumball machines. In Mario, anything with mean eyes that's coming at you is probably bad. In Sonic, all of the enemies are robots, so if it's mechanical you probably don't want to mess with it.
I have two main enemy types planned for NullAwesome: Altean guards and robotic drones, with a few subvariants of each. I think I actually want to go one step further and make all of the enemies conform to a specific color scheme, one which contrasts with Lorn himself and matches the ZetaCorp logo -- just so that when the player sees those colors, they think "enemy".
Lesson 3: Put enemies and hazards in the player's way, but not in their immediate path.
So which seems more fair to you? This:
L = Lorn E = Enemy # = Ground = = Elevator ==== L E E ##############################################
Or this:
L = Lorn E = Enemy # = Ground ==== = = Elevator ==== E E ######################## ######################## L ######################## ##############################################
In both scenarios, once you're at eye level with the enemies, they can spot and shoot you. In both scenarios, the player can see that the enemies are in their way and they must defeat or evade them in order to pass. However, in the first scenario, the player will encounter the enemies simply by walking right. The second scenario puts the enemies up on a ledge, so that the player can't easily stumble onto them. They can stay on the lower level and have a good think about what to do. They still have the same need to overcome the enemies in order to continue, but this time they can choose to engage the enemy on their own terms. Messing with the player's ability to think, plan, and choose is death for your video game.
The same goes for hazards. Again, which seems more fair? This:
L = Lorn # = Ground ! = Lava ## ## L ##############!!!!!!!!!!!!!!!!!!########## ##############!!!!!!!!!!!!!!!!!!##########
Or this:
L = Lorn # = Ground ! = Lava ## ## # # L # # ############## ########## ############## ########## ##############!!!!!!!!!!!!!!!!!!##########
Maybe the player can stumble into enemies and hazards in the later levels, when they've mastered the controls a bit more. But in the early stages at least, it's better to keep the player out of danger until they've decided to engage the danger by moving forward.
Lesson 4: Surprises in the game should be pleasant. Unpleasant surprises should give the player at least 1 to 2 seconds to react.
"Pleasant surprises" are things like the invisible block that yields a 1-up in Mario. "Unpleasant surprises" are things like being transported to a room full of enemies with the door locked, and you have to defeat them all to get back out. If the player comes across an unpleasant surprise, the player should be given a) a way out, and b) time enough to assess what's going on and react accordingly. Unpleasant surprises that kill the player instantly are to be avoided altogether.
This isn't really a hard and fast rule. An unpleasant surprise that kills the player can be fun in a troll game like Portal, old-school text or graphical adventures, or "kaizo" platformers. But in order for it to be fun, death has to be cheap or otherwise not frustrating. In kaizo platformers, the extreme difficulty and high risk of death is supposedly part of the fun. But in Bubsy, the game goes along relatively consistently (by Bubsy standards anyway) then WHAM! You're killed by a... gumball machine? Or go into a pipe that leads directly into deadly water or something. That's just bad design.
Since NullAwesome is not a kaizo platformer, cheap deaths should be avoided and deliberate tight spots should be carefully designed to give the player time to think and react. If the player is taken by surprise, it should be with rewards and not punishments.
So those are some of the things I've learned from everyone's least favorite bobcat. Because if Bubsy can teach us anything, it's all the things that can "paw"-sibly go wrong in your game design.
3 notes · View notes
nullawesome · 8 years ago
Text
My Osana: The Input State Machine
Hi folks. Sorry I've been gone this long while.
You know Yandere Simulator? That idiot game where you play a Japanese schoolgirl and have to sabotage and/or end the lives of other Japanese schoolgirls so you can be with your "senpai"? Fun fact: the YouTube video series made by that game's creator inspired this very blog about the development of my stupid game. I mean, I don't want to hold him up as a hero or anything because he's kind of a jerk, his game is poorly coded and he doesn't seem able to commit to a plan or even a scope for his project, instead spending all his time messing around with what interests him at any given moment.
But look, I'm not really here today to rant about that guy or his game. I'm bringing them up by way of analogy. See, in Yandere Simulator, you're supposed to dispose of your rivals to get the boyfriend, right? Well, the developer of that game has been putting off implementing the first rival, Osana -- because while faffing about with Unity and premade assets is easy, actually implementing a set of real game mechanics is hard. I know, from experience. In developing NullAwesome, my "Osana" has been the modal UI. The plan is to have a player tap a button to enter "hack mode", and then tap an on-screen target to initiate a hack. Hack targets would be determined by entities with a HackTarget component. But in order to do that I would have to implement each mode in terms of UI display, and of response to touch events, as well as a way to transition between modes.
Without the hacking mechanic, there is no game. Hacking, like Jar Jar, is the key to all this. And hence getting this modal UI correct, in a maintainable way, is also key. By putting this off I've essentially held up development of the entire game. I really have no excuses for this other than that this was always a spare-time project for me.
The approach I've chosen is to have a state machine which is globally accessible through a slot in the player entity's PlayerInfo component. There are four states: TITLE, MOVEMENT, HACKING, and PAUSED; these are specified in the InputState enum. The ButtonRenderAgent will draw different UIs depending on the current state, and the NAView will respond differently as well. This is necessary because no actual UI controls are used in-game. Buttons are just bitmaps drawn to the screen, like any other sprite; input events come in the form of touch events to different areas of the screen. All determination of whether a button is pressed, held down, etc. comes from determining whether the input event falls within that button's bounding box.
I suppose I could go the route of implementing what amounts to a UI framework in game, creating button objects and objects which represent different active UIs. But for the small number of states, and small number of buttons, what I have so far appears to work fine.
I'm proud to say that after months of delay (I make no excuse for this), I've started to move the needle on getting this implemented. A transition from the MOVEMENT state to the HACKING state is possible by tapping the hack button; by tapping the (currently invisible) back button in the upper left corner, you can return to the MOVEMENT state.
Next up is implementing HackTargets. And drawing that back button.
3 notes · View notes
nullawesome · 8 years ago
Text
NullAwesome Rendering Performance Is Really Disappointing
I’m not happy, Bob.
NOT. HAPPY. Ask me why.
The answer is because I’ve tested on multiple devices, and the rendering performance is all over the map. None of it is particularly good; but the phones (a Google Nexus 4 and a Samsung Galaxy Note 3) tend to fare better than the tablet (Nexus 7 (2013)).
These are not top-end devices in 2016, and they’re even more behind in 2017. Yet a simple 2D sidescroller should be well within their bailiwick. I have some hunches as to what may be causing the slowdown. I think that the way I’m drawing the tiled background -- a call to DrawBitmap for each drawn tile -- may be throttling the performance. That’s a lot of draw calls, and a lot of bouncing back and forth between the Java runtime and the underlying library code which was probably written in C, C++, or similar. Getting the number of draw calls down would involve pre-rendering the visible tiles into a bitmap and drawing that, updating the set of visible tiles from the tile map as the screen scrolls. It’s complicated, but would require only two draw calls max to put the tile map on the screen, plus a few more to update the tile map’s back buffer.
If that doesn’t work I’ll have to rip the whole works out and use OpenGL. I am so looking forward to that.
And if that doesn’t work... well, I dunno. There’s something in my update loop slowing things down. Maybe this kooky entity idea didn’t pan out as well in practice as in theory. I don’t know.
More on this as it develops. Here’s hoping I have good news.
0 notes
nullawesome · 9 years ago
Text
Data Driven Game Development: Exploits in NullAwesome
Levels in NullAwesome are called exploits, playing on the dual meaning of the term: one sense meaning adventures or daring feats, and the other sense meaning the way a hacker penetrates a system. The exploits of the game make for a good starting point for talking about what I like to call data-driven game design. This means that the way the engine behaves is, in part, dependent on data. In NullAwesome, this data often takes the form of JSON documents.
Any given exploit is determined by an instance of the StageInfo class. StageInfo determines the layout of the level through an array of regions, which are basically rectangles whose dimensions are reckoned in units of tiles (TILE_SIZE pixel by TILE_SIZE pixel squares). Associated with each region is a type, an integer which determines things like what happens when you stand on the region. (Is it a region of ground? hot lava? That sort of thing.) There is also a class TileMap, which holds a big rectangular array of tiles; but it is not the source of truth for the level layout. It is generated from the StageInfo.
StageInfo also holds an array of things, which are basically the locations at which to spawn movable, non-tile game objects such as enemies, elevators, doors, and the like. Again, each thing has a type. Things are not currently implemented.
The beauty part is, all the information about an exploit that StageInfo has can be read from a JSON file; and indeed the loadStage method does just that: read in the regions, things, and their types from a JSON object, populate the appropriate instance variables, and generate the TileMap. If I wanted to, I could even have the engine read the JSON file from an easily accessible place (such as the SD card) for debugging and level development, so that changing the level layout involves changing just one file on the Android device rather than building and uploading a whole new APK.
Another thing I haven't implemented yet, but want to touch on, is a step in the tile-map generation process called greeblization. The region type doesn't specify a specific tile; rather it specifies a class of tiles all of which behave the same. But they can look different; and the greeblization process would be a way to algorithmically generate interesting scenery by altering the tiles within a given type that get used. The top surface of the ground, or the corners and edges of a platform, may for instance be altered to give it a nice appearance. This would happen automatically, as the TileMap was generated from the StageInfo. I will get more into greeblization as I write it.
"Greeblization" comes from "greebles", the little bits of detailing added to a miniature, prop, or costume in film special effects that make it look cool and more believable. For example, the miniature for the Imperial Star Destroyer from Star Wars had lots of bits and bobs from various model kits glued to it, to suggest tanks, ductwork, machinery, and other detailing and make it look more like an actual functioning starship rather than just a vaguely tetrahedral slab. Greeblizing the tiles in a NullAwesome exploit is intended to lend a similar air of believability to the scenery; but probably won't be done until the engine itself handles better.
3 notes · View notes
nullawesome · 9 years ago
Text
Entities and Components in NullAwesome
Remember the 90s when object-oriented programming was all the rage? There was not only time for Klax, there was also apparently plenty of time to toss your old structured programming code, learn new buzzwords like "encapsulation", "inheritance", and "polymorphism", and come to grips with bundling your operations together with your data in "methods" rather than writing functions or procedures to operate on the data.
The object-oriented craze hit the mainstream around the same time GUIs did -- in the late 1980s and the early 1990s. I don't believe this to be a coincidence, as GUIs -- with their hierarchy of nested controls distinguished by polymorphic behavior in response to keyboard and mouse events -- seemed a close fit to be modelled in an object-oriented fashion. At the time, it seemed like games were going to be an even closer fit.
Now, a game engine generally consists of three parts running in a loop: an input phase, during which keyboard and mouse input is collected or responded to; an update phase, during which the game state -- or status of all the objects in the game world -- is updated for the next timer tick; and a render phase, during which the game world as represented by the current game state is drawn on the screen. Depending on the speed of your rendering routines, there could be multiple update ticks per rendering frame. If you have an object-oriented hammer, this looks like a perfect nail to beat into the wood with class hierarchy: simply create classes that represent game objects and have update() and render() methods which can be invoked at the appropriate points in the game loop, and then put instances of that class in an array list or linked list and loop through them, calling their update() and render() methods. This is more or less how SpriteCore, my first attempt at a game framework originally written over 20 years ago, works.
Here's a concrete example. Let's say you were going to write Super Mario Bros., or a blatantly copyright-infringing clone thereof, in C++. You think to yourself: hmm, a lot of the objects in the game, such as Mario himself, the enemies, the power-ups, and even the coins have lots of things in common, such as a location, (possibly zero) velocity, and an image or animation to be drawn on the screen. I know! I'll create a base class, GameObject, that encapsulates all of these and then subclass it for more specific objects. The subclasses will extend the base class with information and state relevant to that particular game object. I'll create a Player class for Mario himself, an Enemy class for things that can hurt him, a Powerup class for things that power you up -- wait a tick, the Powerup class should be a subclass of the Collectible class, because there are things like coins and 1-ups that don't power you up but you can nevertheless collect them... and so on and so forth.
Well, I ran into some problems when attempting to write games with this method. Let's say you want to model an object that's affected by gravity. Do you put it in the GameObject class? Hold up there, shorty: there are some objects -- such as Lakitu and the Super Leaf that turns you into Raccoon Mario -- that aren't affected by gravity. So what do you do? Typically GameObject or some other base class will have a set of flags associated with it. Do you set an AFFECTED_BY_GRAVITY flag in objects of that class? Okay, fine. You're breaking the class abstraction but whatever. But what if there is state associated with being affected by gravity, like the acceleration induced by gravity or the center of the pull? (We're getting pathological now, and turning this game into 2D Mario Galaxy, but whatever, just go with it.) Then objects with the AFFECTED_BY_GRAVITY flag will use that state but objects without that flag will just have that extra memory sitting there, useless.
Aha, but what if we could compose object types by specifying traits, with state and behaviors that are unique to each trait? So you could say something like:
class Mushroom <AffectedByWalls, AffectedByGravity, Powerup> { .... } class Lakitu <Enemy> { .... }
and have all of the behaviors of the various subclasses AffectedByWalls, AffectedByGravity, Powerup, and Enemy incorporated into those classes, leaving to you only the need to specialize the classes with behaviors unique to those objects?
Some programming languages allow this in their object systems, with extensions to object-oriented programming actually called "traits" (or sometimes "mixins"). C++ and Java do not allow this sort of style easily. It can be done in C++ with multiple inheritance or in Java with interfaces, but there is more boilerplate that must be written in order to make it all work. One thing I leanred in the course of writing SpriteCore (and trying to write games with it) is just how hard it is to make this work. The behavior of any given sprite is scattered across many different classes and hence source files. If you want two object types to share a common behavior, such as floating or shooting fireballs, you have to either implement that functionality separately in each class, or put it as a method in their closest shared ancestor class and call it from within the subclasses. But this risks exposing a float() or shootFireball() method to classes that don't need it, and all such classes have to have instance variables associated with those, which they will never use. (Perhaps fireball-shooting things keep track of the fireballs they shot.)
There are ways around these problems, but the way modern game developers seem to like best is called the entity-component system, or ECS. Instead of a bag of GameObjects, in ECS the game world is represented as a state vector in a configuration space. So what that means is it's a real mathy way of saying -- okay, say I'm in 3-dimensional space and you want to find my position. x, y, z coordinates; a vector of 3 elements, 3 dimensions. But now say you also want to track my orientation, how I'm rotated in 3 dimensional space. So now describing my exact configuration -- position and orientation -- takes a 6-dimensional vector.
But it gets better. Say my friend Jay is in space with me, and you want to describe both my position and orientation and his. You can do so with a 12-dimensional vector -- 3 for my position, 3 for my orientation, 3 for Jay's position, and 3 for Jay's orientation. The vector space in which our positions and orientations are reckoned with these long vectors is called a configuration space. So now expand this thought to encompass an entire game world, where you track not just the position and orientation of everything in the game world, but also whether it is alive or dead, whether it is floating, how much health it has, how much ammo or MP it has, and so forth in a great big configuration space with a very large dimension.
Another way to put it is say that your game world is in a big spreadsheet, with one row for each game object and as many columns as there are variables in that object's state. Changes made to the sheet are reflected in the game world on the rendering step. The columns in the sheet are divided into components. Each component is a group of variables -- a data structure -- which describes some aspect of the entity. So no longer do you have a Player class, an Enemy class, a Powerup class, etc. A Player is an entity that has all the components necessary to make a Player work (so things like a position and velocity, but also things like current power-up state or HP or even a structure that maps user input to player actions). An Enemy might have many of the same components that a Player has, but include one that specifies how its AI will behave. A Powerup is an entity that has all of the components necessary to make a Powerup work (so position and velocity again, but also some component which describes the effect the Powerup will have when picked up). And so forth.
So actually, you can basically model your entire game as a single function which takes a game state, user input, and the time and yields a new game state. But you don't have to. First of all, we're working in Java so it's okay to update the state vector in-place. Secondly, we can break these functions into chunks, which for the time being I'll call updaters. In conventional ECS terminology these are called "systems", but I'm going to call them updaters just to be clear (or less blurry).
Each updater runs each timer tick and operates only on those entities which it's interested in, but it does them all at once. So a basic updater would go through the entity table, look at all entities that have a position and a velocity (generally these will be integrated into one "physics" component), and update the position by adding the velocity on each timer-tick. Another updater may check for collisions between players and enemies, and update the player's status to "dead" (or decrease its HP) accordingly; depending on conditions (such as a player landing on an enemy's head) it may decide to kill the enemy instead. Yet another updater will decide what actions the enemy will take to attack the player: walking, flying, homing in on the player's location, etc. So it's the updaters which are the basic classes we'll define in this new world order of gaming.
We'll also define some renderer classes, which run during the rendering phase and are responsible for basically drawing the game world on the screen. These work on the same principle: each renderer examines only the entities it's interested in and draws them all at once. So for a 2D game like NullAwesome you might have a background renderer which draws a tile-mapped background, a sprite renderer which draws individual bitmaps, etc.
Entity-component systems have several advantages over the "bag of subclassed GameObjects". For one, it favors composition over inheritance, which is an object-oriented catchphrase meaning that instead of relying on subclasses to extend functionality, it encourages the creation of objects by gluing together relevant -- well, components. For another, if you are working in a language like C++ (which we aren't for NullAwesome), an ECS lets you lay out the game data in any way you see fit, allowing the updaters to update the game state in a way that minimizes cache misses. On modern CPUs, this can translate into huge performance benefits.
ECS game engines are more data driven which has its own benefits. For example, if you avoid the use of pointers in your game's components, you also get a win when you go to implement save games: simply copy the contents of the entire game state vector from memory to disk and you have a save file. By double-buffering the state vector, you can have all updates go into the "back buffer", ensuring that all updaters draw their knowledge on the current state of the world from an unchanging state. Since entities are just data, you don't have to be locked into a particular language's object system, making interfacing other programming languages such as Lisp, Python, or Lua to an ECS easier. You can even write ECS-based game engines in plain C.
Finally I find ECS based engines to be more testable. All you need to test an updater is the state vector itself, the updater, and whatever classes that one updater depends on (which is generally very few). There's no need to mock out the world.
There are drawbacks: ECS seems to be a more convoluted, less clear way of describing game behavior, especially if you grew up with object-oriented programming. It seems to make much more sense to say "if an enemy sees the player on this game tick, the enemy should fire at the player" by defining seesPlayer() and fire() methods in some Enemy class and calling them each tick, then to write procedures which update some memory table. But as I found out -- and as I would come to discover the developers of StarCraft found out way ahead of me -- writing things the traditional bag-of-GameObjects way also leads to convolution and lack of clarity, even if it doesn't seem like it would at first glance.
The entity-component system in NullAwesome is a fairly straightforward implementation. Each entity is referenced by an integer called an eid (for Entity ID). The EntityRepository class, a singleton, stores the component info for each entity (components are Plain Old Java Objects with all-public instance variables; each component type is a different class). It does this with a hashtable mapping component classes to arrays of length MAX_ENTITIES. For any given eid x, the element in the x th slot in, say, the SpriteMovement array will contain the SpriteMovement component for the entity whose eid is x.
All this is handled for you behind the scenes by the EntityRepository's interface. Here are the highlights:
get(): Call EntityRepository.get() to retrieve the app's one and only Entity Repository.
newEntity(): Allocates a fresh new entity and returns its eid, or throws an exception if the entity table is full.
removeEntity(int eid): Removes the entity with eid eid from the table. This eid may be subsequently reused on future calls to newEntity().
addComponent(int eid, Object comp): Adds a component comp to the entity whose eid is eid. The entity will be flagged as having a component of comp's class as determined by the Java runtime; i.e., the most specific class to which comp belongs. Can throw an exception if eid does not exist.
getComponent(int eid, Class kls): Returns the component of type kls belonging to entity of id eid, or null if no such component. Throws an exception if eid does not exist.
processEntitiesWithComponent(Class kls, EntityProcessor p): Calls p's process() method on all entities having a component of type kls.
findEntityWithComponent(Class kls): Returns the eid of the first entity having a component of type class. Some entities -- such as the one representing the current level -- are singletons by design, which is where this method comes in handy.
Updaters conform to the UpdateAgent interface; renderers conform to the RenderAgent interface. Both kinds of "agents" often use the EntityRepository's processEntitiesWithComponent() method to perform the same operation across a selection of entities in the system.
Here are some of the component types I've written so far for NullAwesome:
SpriteMovement: contains position, velocity, and acceleration information. It's not only used for in-game sprites but it also determines, for example, where the screen is scrolled to in the level.
SpriteShape: Determines the image and animation a particular sprite has. Animation data is loaded from JSON, which I may get into in a future post.
PlayerInfo: Info unique to the player character.
StageInfo: Info about the current level, including a TileMap object representing the level's layout.
There will of course be many more.
So that's a quick look at entity-component systems and the implementation of such we're using in NullAwesome. Feel free to browse the source for more implementation details!
2 notes · View notes