-
Notifications
You must be signed in to change notification settings - Fork 6
Home
Goals: be a simple and re-usable framework for browser-based, console-style 2-d RPGs. (e.g. Dragon Quest, Phantasy Star, Final Fantasy, Ultima, etc.) Allow game developers to focus on content and on implementing their game's unique mechanics instead of reinventing the wheel. Be flexible enough to let a game easily use none, some, or all of the default behavior while overriding the parts they want to change. Provide modular components such as menu-system, battle system, top-down map view, first-person maze view, etc. so that a game can use just the modules it needs, or swap in replacements for some of them. (e.g. one game might use first-person maze view only, with no top-down map, and menu-based battle system. another game might use top-down map view only and tactical battle system. etc.) Support easy export to delivery formats compatible with iOS, Android, and Steam game stores. Be open-source with a non-restrictive license.
Non-goals: full 3d. Real-time action-based combat. Massive-multiplayerness.
Overview of main concepts:
-
mixin-based inheritance. allows easy "inheritance" from multiple "parent" classes. E.g. the PlayerCharacter object uses mixins to inherit from both the MapSprite (also used for NPCs in towns) and the Battler (also used for monsters in combat).
-
UI switching. The typical JRPG is pretty weird from a UI design persepctive, if you think about it. It's a collection of disparate modes in which the same input does different things. E.g. pressing the up-arrow could mean "walk north", "advance forward down this cave corridor", "sail your ship north", "pick between 'yes'/'no' responses to an NPC", "pick a spell/item to use", "pick a monster to attack", etc. depending on the context.
to handle this, RPGbase provides a set of input mode classes which all follow an InputMode mixin (or they will once im done refactoring): battle system, map screen, field menu, dialog, and maze screen are the main ones. A game can instantiate any number of these but only one is active (i.e. receiving the player's input) at one time.
(if your RPG design calls for "minigames" you could implement these as new InputModes.)
-
Serialization: to enable game state to be saved to a save game "file" (actually a database entry on the server), objects can make themselves serializable using the SerializableMixin (and implementing custom serialize/deserialize behavior if needed). The serialize method converts an object's state to a JSON string, and the deserialzie method converts JSON string back to object state. (So when loading a game you'd instantiate an object with no state and then call its deserialize method with JSON loaded from the save-game file.) (this implies that all serializable objects should have a state-free constructor).
-
Game event listeners: (refactoring note: there are currently several different methods here which are all implemented in slightly different ways, i'm trying to combine them. )
-
Stats agnosticism: rpgbase assumes that player characters and monsters will have numbers called "stats" (implemented in a BattlerMixin that is mixed in to both PCs and monster classes) that need to be tracked, but it doesn't care what those stats are called or what they mean in terms of game logic. There are methods for setting, getting, and modifying stats by name. so if you want a stat called "strength" all you have to do is call pc.setStat("strength", 10) and tada, the pc now has a strength of 10.
-
Menu stacks: there is a MenuSystemMixin used by the battle system, the field menu, the dialog system, and any other menu input modes you want to create. it defines a stack of menus where the top menu of the stack is the one that gets the user input. New menus can easily be created (with a list of names for commands and a callback function for each command) and pushed onto the stack. when the user hits the confirm button, the currently highlighted item in the top menu has its callback function executed. When they hit the cancel button, the default is for the top menu to be popped off the stack, returning the focus to the previous menu. If the bottom most menu of the stack is popped off, the default is for the input mode to end and control to be returned to the previous InputMode. However, this behavior can be turned off for the case of menu systems that, for game reasons, the player is not allowed to freely exit -- i.e. the Battle System and scripted plot events.
-
Menu implementations: you have a choice of two different menu system implementations: CSS based and canvas based. CSS menus create text
s in the HTML page, meaning they are actually text, can be styled using CSS, etc. The player could select text from the menus with their mouse and copy it if they so desire. Canvas menus are rendered inside the same element as the rest of the game's graphics, so they are not text but pictures of text. Meaning they automatically scale along with the graphics if the canvas scale is changed. both have their advantages which is why i'm supporting both options, at least for now. The differences between the two implementations are abstracted away from the rest of the code. -
the map screen: many "maps" can be defined, but only one is the active map -- the one the player is on. The MapScreen singleton object is in charge of rendering the map, processing input (it is an InputMode), using that to update the player's location, rendering the walking animation, scrolling the map, etc.
-
the battle system: a specialized menu system instance, the BattleSystem is a singleton object and an InputMode.
-
the player object represents a party of multiple characters, together with some miscellaneous metadata. the player can have "resources". In the same way a Battler tracks any stats that you care to name, the player object will track any resources that you care to name. Unlike stats, resources are shared among the whole party. One game can give a player 100 "gold" while another game gives a player
Each player character is a MapSprite and as such can be added to a Map at a given (x,y) location. Not just player characters but any object which is a MapSprite can be added to a Map and animated by the MapScreen. Besides the player, other MapSprites include NPCs, vehicles, and treasure chests.
A Map is an object built around a 2-dimensional array of Terrain Codes. The meaning of particular TerrainCodes is completely game-dependent; rpgbase doesn't care. Each terrain code corresponds to a "square" where a MapSprite can stand. MapSprites have a "canCross" method which takes a TerrainCode and returns true or false; the MapScreen will not let a map sprite enter any square that has a terrain code that produces a false output from this method. (Thus you can make one MapSprite that is able to cross water tiles and another MapsSprite which is not, etc.)
The number of on-screen pixels that each "square" takes up is easily configurable in the MapScreen object as is the number of horziontal and vertical squares that will be shown in the game viewing rectangle (i.e. the Canvas) at one time.
A Map can be either a TileMap or a SingleImageMap which are rendered differently but otherwise treated teh same. A TileMap uses the TerrainCodes to look up slices of a "texture" file (simply a .png), which are then laid out in a grid to render the map. For instance, if one of the terrain codes is 18, and the MapScreen uses 50 pixels per square. a TileMap would read its texture file, use the 18 as an offset, grab the 18th 50x50-pixel "square" of the texture file, and display that slice of the texture for every tile with a terrain code of 18.
key concept for the map screen is onStep handlers. You can register an onStep handler to a map and set it to be triggered by a certain x-y location, or triggerd by "the edges of the map", or triggered by certain terrain codes. So one function that's called when the player steps on the square x=100, y = 25, another function called when the player steps on a forest tile, and a third function called when the player steps off the edge of a town map. You can register any number of onStep handlers, and even assign them numerical priorities to define which one will take effect if multiple handlers are triggered with the same step.
maps can be set to wrap around north-to-south, east-to-west, both, or neither.
connections between different maps are implemented as onStep handlers that have the effect of replacing the active map with another map. (For example, a staircase connecting two dungeon levels is an onStep handler in the upper map with the effect of moving to the lower map, and a second onStep handler in the lower map with the effect of moving to the upper map.) There is no assumed world geometry; you can connect maps in any crazy way you can think of, but the typical game will use, for instance, town maps with an "edge" handler that takes the player to an overworld when walking off the edge of the town map, etc.
main files:
these two files are basic low-level stuff which is not really specific to RPGs:
-- webClientUtils.js implements some basic animation utilities, an asset loader which handles image loading, and an audio player for sound effects and music.
-- keyboardClasses.js implements a couple of different styles of keyboard input listeners
the following four files contain the real core of rpgbase's logic:
-- menuClasses.js implements the basic menu system, including CSS and Canvas implementations. -- worldMapClasses.js implements maps and the map screen -- playerClasses.js implements map sprites, player character objects, and the player object -- encounterClasses.js implements the skeleton of a combat system. since combat mechanics are one of the things which differ the most between games, the combat system here is designed for client code to flesh out by setting a bunch of callback functions.
the following files are sort of like add-ons (not every game will need to include them, they can be included or not, ala-carte):
-- maze.js is a primitive (8-bit style) first-person maze view. -- scriptedEvent.js defines a PlotPoint class, which lets you script dialogue, npc movement, forced PC movement, showing images on screen, etc. etc. used for in-engine "cutscenes" -- serialize.js defines the serializable mixin, containing the logic for saving a game to a JSON string and loading a saved game from a JSON string.
The last piece needed is the glue code that combines all of the above features into a single easy-to-use generic framework. So far I have made two mutually incompatible and not very successful attempts at doing so:
-- genericRPG.js started out as some ad-hoc glue code but unfortunately I never architected it properly and it has become seriously entangled with the needs of a single game (Moon Serpent). There is a lot of useful code in here but it needs mega refactoring to be as generic as the name implies. Should not be used for new projects.
-- gRPG.js is the successor to genericRPG.js. It is still very bare-bones and needs a lot more feature work before it's ready to use for new projects.
Refactoring notes: -- use a single consistent message-passing architecture for everything -- e.g. take the system that's currently used just for battle messages ("take damage" and "heal" mostly) and use that for everything -- be perfectly clear about the relationship between setting a class to respond to a message in a certain way, setting an instance to respond to a message in a certain way, and setting a mixin to respond to a message in a certain way. e.g. a message sent to a PC goes first to the PC's personal handler, then the handler for PCs in general, then the handler for battle sprites in general. -- one place you can send messages to is "battle display" which will take the top message off the queue every X seconds to display. this is how we can solve the current problem of battle messages getting lost because they're instantly overwritten. -- have a mixin that adds support for message-passing to a class -- do we need a central message dispatcher? like this one here? http://gamedevelopment.tutsplus.com/tutorials/how-to-implement-and-use-a-message-queue-in-your-game--cms-25407