-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added support for gamepad #164
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,27 +10,104 @@ import { | |
import { Controller } from "jsnes"; | ||
import ControlMapperRow from "./ControlMapperRow"; | ||
|
||
const GAMEPAD_ICON = "../img/nes_controller.png"; | ||
const KEYBOARD_ICON = "../img/keyboard.png"; | ||
|
||
class ControlsModal extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { keys: props.keys, button: undefined, modified: false }; | ||
this.state = { | ||
gamepadConfig: props.gamepadConfig, | ||
keys: props.keys, | ||
button: undefined, | ||
modified: false | ||
}; | ||
this.handleKeyDown = this.handleKeyDown.bind(this); | ||
this.handleGamepadButtonDown = this.handleGamepadButtonDown.bind(this); | ||
this.listenForKey = this.listenForKey.bind(this); | ||
|
||
this.state.gamepadConfig = this.state.gamepadConfig || {}; | ||
this.state.gamepadConfig.playerGamepadId = this.state.gamepadConfig | ||
.playerGamepadId || [null, null]; | ||
this.state.gamepadConfig.configs = this.state.gamepadConfig.configs || {}; | ||
|
||
this.state.controllerIcon = this.state.gamepadConfig.playerGamepadId.map( | ||
gamepadId => (gamepadId ? GAMEPAD_ICON : KEYBOARD_ICON) | ||
); | ||
this.state.controllerIconAlt = this.state.gamepadConfig.playerGamepadId.map( | ||
gamepadId => (gamepadId ? "gamepad" : "keyboard") | ||
); | ||
this.state.currentPromptButton = -1; | ||
} | ||
|
||
componentWillUnmount() { | ||
if (this.state.modified) { | ||
this.props.setKeys(this.state.keys); | ||
this.props.setGamepadConfig(this.state.gamepadConfig); | ||
} | ||
this.removeKeyListener("keydown", this.handleKeyDown); | ||
this.removeKeyListener(); | ||
} | ||
|
||
listenForKey(button) { | ||
this.setState({ button }); | ||
var currentPromptButton = button[1]; | ||
|
||
this.removeKeyListener(); | ||
this.setState({ button, currentPromptButton }); | ||
this.props.promptButton(this.handleGamepadButtonDown); | ||
document.addEventListener("keydown", this.handleKeyDown); | ||
} | ||
|
||
handleGamepadButtonDown(buttonInfo) { | ||
this.removeKeyListener(); | ||
|
||
var button = this.state.button; | ||
|
||
const playerId = button[0]; | ||
const buttonId = button[1]; | ||
|
||
const gamepadId = buttonInfo.gamepadId; | ||
const gamepadConfig = this.state.gamepadConfig; | ||
|
||
// link player to gamepad | ||
const playerGamepadId = gamepadConfig.playerGamepadId.slice(0); | ||
const newConfig = {}; | ||
|
||
playerGamepadId[playerId - 1] = gamepadId; | ||
|
||
const rejectButtonId = b => { | ||
return b.buttonId !== buttonId; | ||
}; | ||
|
||
const newButton = { | ||
code: buttonInfo.code, | ||
type: buttonInfo.type, | ||
buttonId: buttonId, | ||
value: buttonInfo.value | ||
}; | ||
newConfig[gamepadId] = { | ||
buttons: (gamepadConfig.configs[gamepadId] || { buttons: [] }).buttons | ||
.filter(rejectButtonId) | ||
.concat([newButton]) | ||
}; | ||
|
||
const configs = Object.assign({}, gamepadConfig.configs, newConfig); | ||
|
||
this.setState({ | ||
gamepadConfig: { | ||
configs: configs, | ||
playerGamepadId: playerGamepadId | ||
}, | ||
currentPromptButton: -1, | ||
controllerIcon: playerGamepadId.map(gamepadId => | ||
gamepadId ? GAMEPAD_ICON : KEYBOARD_ICON | ||
), | ||
modified: true | ||
}); | ||
} | ||
|
||
handleKeyDown(event) { | ||
this.removeKeyListener(); | ||
|
||
var button = this.state.button; | ||
var keys = this.state.keys; | ||
var newKeys = {}; | ||
|
@@ -39,6 +116,11 @@ class ControlsModal extends Component { | |
newKeys[key] = keys[key]; | ||
} | ||
} | ||
|
||
const playerGamepadId = this.state.gamepadConfig.playerGamepadId.slice(0); | ||
const playerId = button[0]; | ||
playerGamepadId[playerId - 1] = null; | ||
|
||
this.setState({ | ||
keys: { | ||
...newKeys, | ||
|
@@ -48,12 +130,23 @@ class ControlsModal extends Component { | |
] | ||
}, | ||
button: undefined, | ||
gamepadConfig: { | ||
configs: this.state.gamepadConfig.configs, | ||
playerGamepadId: playerGamepadId | ||
}, | ||
currentPromptButton: -1, | ||
controllerIcon: playerGamepadId.map(gamepadId => | ||
gamepadId ? GAMEPAD_ICON : KEYBOARD_ICON | ||
), | ||
controllerIconAlt: playerGamepadId.map(gamepadId => | ||
gamepadId ? "gamepad" : "keyboard" | ||
), | ||
modified: true | ||
}); | ||
document.removeEventListener("keydown", this.handleKeyDown); | ||
} | ||
|
||
removeKeyListener() { | ||
this.props.promptButton(null); | ||
document.removeEventListener("keydown", this.handleKeyDown); | ||
} | ||
|
||
|
@@ -70,58 +163,96 @@ class ControlsModal extends Component { | |
<thead> | ||
<tr> | ||
<th>Button</th> | ||
<th>Player 1</th> | ||
<th>Player 2</th> | ||
<th> | ||
Player 1 | ||
<img | ||
className="controller-icon" | ||
src={this.state.controllerIcon[0]} | ||
alt={this.state.controllerIconAlt[0]} | ||
/> | ||
</th> | ||
<th> | ||
Player 2 | ||
<img | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you tested that this works in the built production bundle? I think you might need to import the images to make sure the calculated paths are correct: https://facebook.github.io/create-react-app/docs/adding-images-fonts-and-files There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great! I alredy tested it using developer run (yarn start) and production build on my local machine. I will test it again on the netlify deploy for this PR (https://deploy-preview-164--youthful-kepler-d6ba79.netlify.com/), only to be sure Do you need a list of covered cases ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice okay. Sounds like it works then. :) |
||
className="controller-icon" | ||
src={this.state.controllerIcon[1]} | ||
alt={this.state.controllerIconAlt[1]} | ||
/> | ||
</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<ControlMapperRow | ||
buttonName="Left" | ||
currentPromptButton={this.state.currentPromptButton} | ||
button={Controller.BUTTON_LEFT} | ||
prevButton={Controller.BUTTON_SELECT} | ||
keys={this.state.keys} | ||
handleClick={this.listenForKey} | ||
gamepadConfig={this.state.gamepadConfig} | ||
/> | ||
<ControlMapperRow | ||
buttonName="Right" | ||
currentPromptButton={this.state.currentPromptButton} | ||
button={Controller.BUTTON_RIGHT} | ||
prevButton={Controller.BUTTON_LEFT} | ||
keys={this.state.keys} | ||
handleClick={this.listenForKey} | ||
gamepadConfig={this.state.gamepadConfig} | ||
/> | ||
<ControlMapperRow | ||
buttonName="Up" | ||
currentPromptButton={this.state.currentPromptButton} | ||
button={Controller.BUTTON_UP} | ||
prevButton={Controller.BUTTON_RIGHT} | ||
keys={this.state.keys} | ||
handleClick={this.listenForKey} | ||
gamepadConfig={this.state.gamepadConfig} | ||
/> | ||
<ControlMapperRow | ||
buttonName="Down" | ||
currentPromptButton={this.state.currentPromptButton} | ||
button={Controller.BUTTON_DOWN} | ||
prevButton={Controller.BUTTON_UP} | ||
keys={this.state.keys} | ||
handleClick={this.listenForKey} | ||
gamepadConfig={this.state.gamepadConfig} | ||
/> | ||
<ControlMapperRow | ||
buttonName="A" | ||
currentPromptButton={this.state.currentPromptButton} | ||
button={Controller.BUTTON_A} | ||
prevButton={Controller.BUTTON_DOWN} | ||
keys={this.state.keys} | ||
handleClick={this.listenForKey} | ||
gamepadConfig={this.state.gamepadConfig} | ||
/> | ||
<ControlMapperRow | ||
buttonName="B" | ||
currentPromptButton={this.state.currentPromptButton} | ||
button={Controller.BUTTON_B} | ||
prevButton={Controller.BUTTON_A} | ||
keys={this.state.keys} | ||
handleClick={this.listenForKey} | ||
gamepadConfig={this.state.gamepadConfig} | ||
/> | ||
<ControlMapperRow | ||
buttonName="Start" | ||
currentPromptButton={this.state.currentPromptButton} | ||
button={Controller.BUTTON_START} | ||
prevButton={Controller.BUTTON_B} | ||
keys={this.state.keys} | ||
handleClick={this.listenForKey} | ||
gamepadConfig={this.state.gamepadConfig} | ||
/> | ||
<ControlMapperRow | ||
buttonName="Select" | ||
currentPromptButton={this.state.currentPromptButton} | ||
button={Controller.BUTTON_SELECT} | ||
prevButton={Controller.BUTTON_START} | ||
keys={this.state.keys} | ||
handleClick={this.listenForKey} | ||
gamepadConfig={this.state.gamepadConfig} | ||
/> | ||
</tbody> | ||
</Table> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This stuff seems to be calculated based on
gamepadConfig
. Perhaps it makes more sense to just be a function or something instead of being on state.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am sorry, I don't get it, it is only a default initialization to ensure gamepadConfig starts in a not null state. Show me a code example if you want
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only a minor thing, no worries. From a cursory look at the code, it seems like
controllerIcon
is just computed fromgamepadConfig
. So, it's not really state, but just a function which would takegamepadConfig
as an argument.I'll get this merged and then make some follow-up issues. :)