{"id":16398061,"url":"https://github.com/jdotrjs/phaser3-hadoken","last_synced_at":"2025-10-26T14:31:28.905Z","repository":{"id":41308897,"uuid":"154406062","full_name":"jdotrjs/phaser3-hadoken","owner":"jdotrjs","description":"Library to help manage / match input sequences for Phaser v3","archived":false,"fork":false,"pushed_at":"2019-02-22T15:15:38.000Z","size":1038,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-31T20:45:09.549Z","etag":null,"topics":["fighting-game","gamedev","input","library","phaser"],"latest_commit_sha":null,"homepage":"https://jdotrjs.github.io/demos/phaser3-hadoken/index.html","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jdotrjs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-23T22:42:28.000Z","updated_at":"2024-05-29T11:35:50.000Z","dependencies_parsed_at":"2022-08-26T03:42:41.287Z","dependency_job_id":null,"html_url":"https://github.com/jdotrjs/phaser3-hadoken","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdotrjs%2Fphaser3-hadoken","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdotrjs%2Fphaser3-hadoken/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdotrjs%2Fphaser3-hadoken/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdotrjs%2Fphaser3-hadoken/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jdotrjs","download_url":"https://codeload.github.com/jdotrjs/phaser3-hadoken/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238347816,"owners_count":19456999,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fighting-game","gamedev","input","library","phaser"],"created_at":"2024-10-11T05:11:44.723Z","updated_at":"2025-10-26T14:31:28.444Z","avatar_url":"https://github.com/jdotrjs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"**Note**: Phaser 3.16 changed the input system in non-trivial ways. This library\nhasn't been tested under that version.\n\n#### Table of Contents\n- [Demo](#demo)\n- [Get it: direct link](#get-it-direct-link)\n- [Get it: `npm` and `yarn`](#get-it-npm-and-yarn)\n- [Using Hadoken](#using-hadoken)\n  - [How it Works](#how-it-works)\n  - [Details and Examples](#details-and-examples)\n    - [Adapters](#adapters)\n    - [Filters](#filters)\n    - [Matchers](#matchers)\n    - [SimpleMatcher](#simplematcher)\n  - [Other uses](#other-uses)\n  - [Even weirder usages](#even-weirder-usages)\n- [Sample project](#sample-project)\n- [License](#license)\n\n## Demo\n\nA super compact (13k minimized, 55k with source map!) library to match fighting\ngame style move sequences and generally help map user input into your game.\n\nA [live demo][demo] is available and a gif is too:\n\n![Hadoken demo gif](https://raw.githubusercontent.com/jdotrjs/phaser3-hadoken/master/README/hadoken_demo.gif)\n\n[demo]: https://jdotrjs.github.io/demos/phaser3-hadoken/index.html\n\n## Get it: direct link\n\nDirect downloads of `phaser3-hadoken` can be had through the\n[releases list][gh-releases] on github. You can then include it as normal by\nscript tag:\n\n```html\n\u003cscript\n  type=\"text/javascript\"\n  src=\"https://github.com/jdotrjs/phaser3-hadoken/releases/download/v0.2.0-r1/hadoken.min.js\"\n\u003e\u003c/script\u003e\n```\n\nI use this approach in the demo. The source is included, see the\n[Sample Project][a-sample] section in this doc.\n\n[gh-releases]: https://github.com/jdotrjs/phaser3-hadoken/releases\n[a-sample]: #Sample-project\n\n## Get it: `npm` and `yarn`\n\n[![npm](https://img.shields.io/npm/dt/phaser3-hadoken.svg)][npmjs]\n\nThis library is published under [phaser3-hadoken][npmjs] and can be added to\nyour project via:\n\n```bash\n# npm\nnpm install phaser3-hadoken --save\n\n# yarn:\nyarn add phaser3-hadoken\nyarn install\n```\n\n[npmjs]: https://www.npmjs.com/package/phaser3-hadoken\n\n## Using Hadoken\n\nHadoken should work for many users with just the provided code. But if it\nis behaves differently than you'd like most aspects of how it processes and\nmatches inputs and be replaced. This README will cover a summary of how it\nworks by default and then get into the details of most of the code that\ncomes with the library.\n\n### How it Works\n\nHadoken can be thought of as an interface between your game and how Phaser\nmanages input. You tell it what an input \"means\" to your game + what sequence\nof inputs constitute a special move and Hadoken will tell you when those things\nhappen. It's worth noting here that \"input\" can be anything. At launch time it\nships with an understanding of how to read Keyboard and Gamepad input.\n\n\u003e _Terminology_: For better or worse I refer to things that your game cares\n\u003e about as \"Semantic Input\" pretty much throughout this doc and in many places\n\u003e within the codebase. I do this because it's makes a clear separation between\n\u003e \"something happened on a controller\" and \"something the game cares about.\"\n\u003e This approcah is useful if you wish to support multiple inputs but only deal\n\u003e with understanding the difference in a single part of your codebase.\n\nThe data flow for an input is:\n\n- Controller adapter \u0026mdash; understands how to read the controller state and\n  reports their state (pressed/unpressed) to hadoken internal state; the\n  adapter is responsible for translating between a controller value and a\n  semantic input, e.g., if the player presses \"A\" the adapter might report\n  that \"Attack\" or \"Jump\" was pressed;\n- Filter chain \u0026mdash; once Hadoken recieves a new input it runs the known\n  state of semantic inputs through a series of filters; this allows\n  modifications to be made such as rewritting input based on player facing.\n  The filter chain operates on input as captured at a single point in time;\n- Matchers \u0026mdash; once the filter chain is complete the input buffer is\n  passed to the collection of matchers to determine if any of the defined\n  moves have been performed;\n- Buffer culling \u0026mdash; Finally the inputs are matched (or not) and we need\n  to clean up after ounselves to limit memory usage.\n\nSo, if that's how Hadoken works how does it inform your game that\n_things are happening_? Well, take your pick:\n\n1. Whenever a move is matched a `Hadoken.Events.Match` is emitted and carries\n   with it a `Hadoken.MatchData`. You can listen for these on `hadokenObj.emitter`.\n2. Hadoken runs in `preupdate` and will set the `matchedMove` attribute to the\n   name of the move that was matched; this check gets run before each update\n   so `matchedMove` will only be set for one frame. That means you can do a\n   simple `if (hadokenObj.matchedMove !== null) { ... }` or the like to check\n   for move matching.\n\nAdditionally, if you care about specific inputs and not move matching you have\na few other options\n\n1. For a point-in-time check you can called `hadokenObj.pressed()` for mapped\n   inputs or, often, adapter specific methods for exposing data;\n2. input begin and end events are also emitted from `hadokenObj.emitter`. They\n   are a `Hadoken.Events.InputUpdate` with a data argument of type\n   `Hadoken.InputUpdateData`.\n\n### Details and Examples\n\nNow that you have a high level understanding of how input works its way through\nHadoken and how it signals to you that moves are matching let's talk in detail\nabout some of those components.\n\n#### Adapters\n\nAs a user of Hadoken you'll probably be creating instances of the adapters based\non which controller you want to use. Each of these adapters take their own config\nthat they are responsible for documenting. Let's look at `KeyboardHadoken` and\nall the pieces that go into configuring it:\n\n```typescript\nexport class KeyboardHadoken extends Hadoken\u003cHadokenKeyboardConfig\u003e {\n  constructor(scn: Phaser.Scene, cfg: HadokenKeyboardConfig) { /* ... */ }\n}\n\ntype MappingFn = (keycode: number) =\u003e SemanticInput | null\n\ntype HadokenKeyboardConfig = HadokenPipelineConfig \u0026 {\n  // responsible for converting from a keycode to game-relevant input\n  keymapFn: MappingFn,\n}\n\nexport type HadokenPipelineConfig = {\n  // indicates how Hadoken should cull the input buffer. If 'depth' then\n  // bufferLimit is the raw number of input states to store; if 'time' then\n  // we will drop frames older than bufferLimit milliseconds\n  bufferLimitType: 'depth' | 'time',\n\n  // argument whose meaning is determined by bufferLimitType\n  bufferLimit: number,\n\n  // a collection of filters that will be applied to the raw input states to\n  // collect the processed state which will be fed into the matchers\n  filters?: FilterFn,\n\n  // list of matchers that will be run in priority order, the first matcher\n  // that returns true will result stop matching process\n  matchers?: MoveDef[],\n}\n```\n\nFor the moment let's ignore `filters` and `matchers`, we'll deal with those\nbelow and in the sample project.\n\nTo create a Hadoken that supports keyboard input there are three things that\nare required: `bufferLimitType`, `bufferLimit`, and `keymapFn`. The buffer\nrelated parameters are common to all Hadoken adapters and control how much\nhadoken will remember when it's trying to find move matches. `keymapFn` is\nspecific to the keyboard adapter and just tells it how to map keyboard keys\nto your game.\n\nTaken all together we can pretty easily create a matcher that will watch\nkeyboard inputs and map them to directions and an action command:\n\n```typescript\nconst key = Phaser.Input.Keyboard.KeyCodes\nconst keymap = {\n  [key.LEFT]:  'left',\n  [key.RIGHT]: 'right',\n  [key.A]:     'action',\n}\n\nconst hadoken = new Hadoken.KeyboardHadoken(scene, {\n  bufferLimitType: 'time',\n  bufferLimit: 4000,\n  keymapFn: k =\u003e !!keymap[k] ? keymap[k] : null,\n})\n```\n\nNow that we understand how to map from arbitrary controller input to something\nmeaningful lets look at applying filters to that input.\n\n#### Filters\n\nA filter is a series of functions that can be applied to a snapshot of input\nstate. Specifically:\n\n```typescript\ntype FilterFn = (input: InputSnapshot) =\u003e InputSnapshot\n\n// Represents a single point at time and the state of any input\nexport type InputSnapshot = { timestamp: number, state: InputState }\n\n// Maps input by name to tracked data about it\nexport type InputState = { [name: string]: InputData }\nexport type InputData = { pressed: number }\n```\n\nTo continue with our example let's say we want the `action` input to differ\ndepending on which item our player is holding. We can start with our previous\nhadoken object and just build a filter that knows how to make that translation:\n\n```typescript\nimport { HasKey, ReplaceKey } from 'hadoken/InputSnapshot'\n\nconst player = { item: null }\n\nconst inputFilter = s =\u003e {\n  // no change if 'action' not pressed\n  if (!HasKey(s, 'action')) { return s }\n\n  // the default action is just to jump\n  let newAction = 'jump'\n  if (player.item === 'boots')  { newAction = 'rocket_jump' }\n  if (player.item === 'sword')  { newAction = 'slash' }\n  if (player.item === 'shield') { newAction = 'guard' }\n\n  // now rewrite the action based on what item the player is holding\n  return ReplaceKey(s, 'action', newAction)\n}\n\nconst hadoken = new Hadoken.KeyboardHadoken(scene, {\n  bufferLimitType: 'time',\n  bufferLimit: 4000,\n  keymapFn: k =\u003e !!keymap[k] ? keymap[k] : null,\n  filters: inputFilter,\n})\n```\n\nOur Hadoken will now map the A button to jump, rocket_jump, slash, and guard\nbased on player state. If you would like to apply more than one filter you can\ncombine a series of filter functions via `Hadoken.Filters.NewChain`.\n\nSome common filter usage is:\n\n- `Filters.CoalesseInputs` \u0026mdash; Combining multiple controller inputs into\n  a single sythesized input, e.g., `up` and `right` getting mapped to a single\n  `up+right`.\n- `Filters.MapToFacing` \u0026mdash; Translates usage of 8-way direction set to be\n  in terms of `forward` and `backward` based on a function that can return the\n  player's facing. See the [sample project][demo-src-facing] for detailed usage.\n- `Filters.OnlyMostRecent` \u0026mdash; only the most recent of a set of inputs\n  is considered pressed.\n\n[demo-src-facing]: https://github.com/jdotrjs/phaser3-hadoken/blob/master/example/ExampleConfig.js#L131\n\n#### Matchers\n\nTo close out let's actually start matching move sequences based on our filtered\ninput. To do that we need to understand how moves are defined for Hadoken:\n\n```typescript\n// Checks an array if input snapshots and returns true + metadata if it matches\n// the associated move\nexport type MatchFn = (history: InputSnapshot[]) =\u003e [boolean, object | null]\n\n// Defines a move that Hadoken should understand\nexport type MoveDef = { name: string, match: MatchFn }\n\n// And, finally, from the Hadoken config:\n// list of matchers that will be run in priority order, the first matcher\n// that returns true will result stop matching process\nmatchers?: MoveDef[],\n```\n\nSo, basically, we can just give the Hadoken config a list of objects that pair\na named move with some function that returns true if the input history fulfills\nits requirements:\n\n```typescript\nimport * as SimpleMatcher from 'hadoken/Common/SimpleMatcher'\nimport { Events, MatchData } from 'hadoken'\n\nconst moveList = [\n  { name: 'dash_stab',   match: new SimpleMatcher([ 'right', 'right', 'slash' ]) },\n  { name: 'shield_bash', match: new SimpleMatcher([ 'right', 'right', 'guard' ]) },\n  { name: 'dodge',       match: new SimpleMatcher([ 'left', 'left', 'rocket_jump' ]) },\n]\n\nconst hadoken = new Hadoken.KeyboardHadoken(scene, {\n  bufferLimitType: 'time',\n  bufferLimit: 4000,\n  keymapFn: k =\u003e !!keymap[k] ? keymap[k] : null,\n  filters: inputFilter,\n  matchers: moveList,\n})\n\nhadoken.emitter.on(Events.Match, (data: MatchData) =\u003e {\n  console.log(`matched move: ${data.name}`)\n})\n```\n\nAt this point hadoken is listening for input, translating inputs based on\nplayer item state, and examining history to watch for one of three special\nmoves then emitting a signal when it has been entered. All of this with a\nrelatively small amount of work.\n\nIntegrating this to your game should be pretty straight forward; instead of\nlogging that a move was matched you would have your character start taking\nthe action indicated by the move that was matched.\n\nSome things aren't handled, though. For example if you wanted to specify\nthat a move must have cooled down for a certain amonut of time before being\nused again this library doesn't handle that... instead you would need to\nhandle managing that state yourself. That may sound complicated but can be\nas simple as:\n\n```typescript\nconst cooldownEmitter = new Phaser.Events.EventEmitter()\n\nconst cooldownSpec = {\n  dash_stab:   1000,\n  shield_bash: 1250,\n}\n\nconst cooldownState = {}\n\nhadoken.emitter.on(Hadoken.Events.Match, (data: MatchData) =\u003e {\n  const reqCD = cooldownSpec[data.name] || 0\n\n  // no cooldown required\n  if (reqCD == 0) {\n    cooldownEmitter.emit(Hadoken.Events.Match, data)\n    return\n  }\n\n  const now = Date.now()\n  const last = cooldownState[data.name] || 0\n\n  // we want to require a cooldown so check to see if it's been \u003e the required\n  // cooldown MS since the move was last performed\n  if (now - last \u003e reqCD) {\n    cooldownState[data.name] = now\n    cooldownEmitter.emit(Hadoken.Events.Match, data)\n  }\n})\n```\n\nYou can then use `cooldownEmitter` in place of `hadoken.emitter` and your will\nreceive at most 1 of a move in however many milliseconds are specified in\n`cooldownSpec` (or no cooldown will be required if a move has no entry, like\n`dodge`).\n\n#### SimpleMatcher\n\nHadoken comes with a simple matcher function that takes a sequence of moves (or\nfor the more advanced case move predicate) and looks for that sequence in its\ninput buffer. It does _not_ require that those moves are sequential. That means\nthat `A A B` would match against all of the following input histories:\n\n- `A A B`\n- `A C A B`\n- `A B C B A B`\n\nThis is to handle allowing for inexact inputs resulting from ... well anything.\nYou can control how permissive the simple matcher is by setting its timing\ntolerances between each step or for the whole input. The defaults are that\neach step of an input must happen with 250ms of the next and the whole input\nmay not exceed 3s.\n\nMore details can be found in code+docs of [SimpleMatcher.ts][src-simplematcher].\n\n[src-simplematcher]: https://github.com/jdotrjs/phaser3-hadoken/blob/master/src/Common/SimpleMatcher.ts\n\n### Other uses\n\nIf you were paying close attention you might have noticed that while Hadoken\nsays it's about matching fighting came inputs on the box it's really just a\ngeneralized input manager. To that end you can use it as an interface to the\nPhaser input system.\n\nSupport for this is currently less well developed than the move-matching\nbehavior but is supported by\n\n1. `Hadoken.Events.InputUpdate` event which contains all the keys pressed\n   or released since the last update;\n2. `hadokenObj.pressed()` which returns an array of all pressed inputs which\n   makes checking for key state trivial in your input loop.\n\n### Even weirder usages\n\nWe've been speaking of adapters as if they can only map from some controller\nthat the user has. But really an adapter could be built around _anything_.\n\n- On screen virtual controls? obvious fit;\n- You want your websocket to generate inputs? okay;\n- What about playback recording? yea, could see that;\n- Got an AI that you want to play your game? A++ go for it.\n\nThe adapter abstraction means literally anything can be used to drive your\ngame, be creative.\n\n## Sample project\n\nThe sample project is currently live on [the demo site][demo] site. The source\n[is available][demo-src] with it's owmn README.\n\n[demo-src]: https://github.com/jdotrjs/phaser3-hadoken/blob/master/example/\n\n## License\n![CC BY-NC](https://licensebuttons.net/l/by-nc/3.0/88x31.png)\n\ntl;dr: Creative Commons BY-NC, basically, means you can use this however you\nwant as long as you:\n\n1. Provide a link back to this library in your credits;\n2. Are not using it in a commercial work.\n\nFull text available [here](https://creativecommons.org/licenses/by-nc/4.0/legalcode).\n\nIf you would like alternate licensing for your project get in touch and we can\nwork something out.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdotrjs%2Fphaser3-hadoken","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjdotrjs%2Fphaser3-hadoken","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdotrjs%2Fphaser3-hadoken/lists"}