What is "onkeyup" in gamepad api? - javascript

I'm experimenting a little with gamepad api. When a button is pressed, it gets "fired" all the time until the button is released. This is cool for something like running or shooting in a game, but problematic, when I want to navigate inside a menu (e.g. going 16 menu items down instead of one).
On a normal keyboard, I would use onkeyup/onkeydown to see, when a button press has ended. But on gamepad api, I didn't find something like that.
JS:
var gamepadPressCounter = 0;
function update() {
const gamepads = navigator.getGamepads();
if (gamepads[0].buttons[0].pressed === true) {
gamepadPressCounter++;
}
window.requestAnimationFrame(update);
}
So, do you know a way to get the gamepadPressCounter only one up per press and not infinite times?

You have to check it against whether it was pressed last time you checked. It doesn't fire events, it is stateful. You are directly checking if the button is pressed or not.
Make a variable to store the state of the button last time you checked and compare it to see if it has changed from last time.
const anyButtonPressedChangeListener = {
listeners: [],
addListener(cb) {
this.listeners.push(cb);
},
signal(state) {
for (const cb of this.listeners) {
cb(state);
}
}
}
let buttonstate = false;
const loop = () => {
const gamepads = navigator.getGamepads();
const gamepad = gamepads[0];
if (gamepad) {
const statenow = gamepad.buttons.some(btn => btn.pressed);
if (buttonstate !== statenow) {
buttonstate = statenow;
anyButtonPressedChangeListener.signal(statenow);
}
} else {
console.log("Not connected");
}
setTimeout(loop, 1000);
}
anyButtonPressedChangeListener.addListener((buttonState) => {
console.log(`Any button pressed changed to ${buttonState}`);
});
loop();

Related

matter.js: collisionStart triggered many times for one collision

I am working on a game app using React native and Matter.js.
I am trying to implement a system that adds points every time a bullet hits a target.
In order to do this, I am trying to use collisionStart to detect the collision.
However, even though the bullet and target collide only once, the event seems to be triggered 41 times.
This is the code:
Matter.Events.on(engine, 'collisionStart', (event) => {
let pairs = event.pairs
for (const pair of pairs) {
if (pair.bodyA.label === 'bullet' && pair.bodyB.label === 'Worm') {
console.log("target hit");
}
}
})
In the end, I'm planning to replace console.log with something that adds points. At the current moment, one collision seems like it would trigger the add points 41 times, which is obviously not ideal.
Any ideas what is happening here and how I can get this to trigger only once for one collision?
Try next example. I take it from my own project [you need little adaptd from ts to js]:
Matter.Events.on(this.starter.getEngine(), "collisionStart", function (event) {
root.collisionCheck(event, true);
});
public collisionCheck(event, ground: boolean) {
const myInstance = this;
const pairs = event.pairs;
for (let i = 0, j = pairs.length; i !== j; ++i) {
const pair = pairs[i];
if (pair.activeContacts) {
if (pair.bodyA.label === "bullet" && pair.bodyB.label === "Worm") {
const collectitem = pair.bodyA;
this.playerDie(collectitem);
} else if (pair.bodyB.label === "bullet" && pair.bodyA.label === "Worm") {
const collectitem = pair.bodyB;
this.playerDie(collectitem);
}
// ....
}
}
}
public destroyBody = (destroyBody) => {
try {
Matter.Composite.remove(this.getWorld(), destroyBody);
} catch(err) {
console.log(err)
}
}
If you still have same problem , we can adapt also with flag PREVENT_DOUBLE_BY_1_SECOUND for example.

How do I make the multiple keydown event work without breaking?

$(document).on('keydown', function(event) {
const current = audios[Number(event.key) // audios is array of music files
current.currentTime = 0;
current.play();
});
I'm creating a drum application. If I press the number 2 with the keydown event, it will be activated. And while I hold down the number 2 and press 3, the number 2 will stop sounding. How can I make it happen? And Why do not this?
It looks like there's a syntax error in your code. const current = audios[Number(event.key) is missing a closing ].
Here is how I would approach it.
const pressedKeys = {};
$(document.body).keydown(function (evt) {
pressedKeys[Number(evt.key)] = true;
playSongs(pressedKeys);
});
$(document.body).keyup(function (evt) {
pressedKeys[Number(evt.key)] = false;
});
function playSongs(dict) {
for (let song in dict) {
if (dict[song] === true) {
audios[song].currentTime = 0;
audios[song].play();
}
}
}
This code keeps track of keys in a dictionary. Whenever a keydown is registered it adds it to the dictionary. Right afterwards, playSongs finds all keys that are true and plays that song.

Creating multiples of the same DOM Element Image

I am a new Developer and am working on a Tic Tac Toe SPA. I have a working game, but I want to customize the game tokens. I have tried creating a DOM element a few different ways, which were all successful. Here is the problem:
Every time I go to make a second move for a player, the DOM image disappears and reappears in the new square selected. Obviously this is not the desired action. Is there something I don't know about creating a DOM element. I have googled this and read countless articles and watched countless videos.
const stark = document.createElement('img')
stark.src = 'https://i.imgur.com/d70XlET.png'
stark.height = 80
stark.width = 80
const lanister = document.createElement('img')
lanister.src = 'https://i.imgur.com/d70XlET.png'
lanister.height = 80
lanister.width = 80
const play = (event) => {
if (gameOver === false) {
if (event.target.innerHTML === '') {
$('#' + event.target.id).append(turn)
}
}
}
'turn' is a variable that works with a toggle function to switch between players and stores whichever players turn it is(i.e. 'stark')
I would be really grateful if someone could point me in the direction of a resource where I could learn more about this.
const player1 = stark
const player2 = lanister
let turn = player1
let prevTurn = player2
const togglePrevTurn = () => {
if (!gameOver) {
if (prevTurn === player1) {
prevTurn = player2
} else {
prevTurn = player1
}
}
}
const toggleTurn = () => {
if (!gameOver) {
if (turn === player1) {
turn = player2
} else {
turn = player1
}
$('#message').text(turn + " 's turn")
}
}
Whenever you use Javascript's appendChild or jQuery's append, when you pass it an element, that element gets removed from its previous location in the DOM (if it's in the DOM), and then gets inserted at the new position. It sounds like what you need to do is explicitly create a new element each time, which you might do with cloneNode().
Also, probably best to name your variables precisely - if turn is an image, name it to make it clear that it's an image, perhaps currentPlayerImage.
In addition, because you already have a reference to the event.target, there's no need to reselect it with $('#' + event.target.id) - just select event.target:
const play = (event) => {
if (gameOver === false) {
if (event.target.innerHTML === '') {
$(event.target).append(currentPlayerImage.cloneNode());
}
}
}

Recommended to use Subject or not

I am using Subject in my code and wondering if it is the recommended to do it in this way.
First of all, for what I am using the Subject is, when the toggle button got pressed, then other buttons will be set as inactive.
After edit got pressed:
Subject definition and subscriber implementation code:
let oButtonSubject = new Rx.Subject();
this._subscribeButtonState(oButtonSubject);
_subscribeButtonState: function (oButtonSubject) {
let self = this;
return oButtonSubject
.subscribe(function (oBtnState) {
let oModel = self.getModel("vmButtonsState");
oModel.setProperty("/edit", oBtnState.bEdit);
oModel.setProperty("/mass", oBtnState.bMass);
oModel.setProperty("/save", oBtnState.bSave);
oModel.setProperty("/cancel", oBtnState.bCancel);
});
},
The code above will set the state of buttons.
Every time, when the edit got pressed, the next method got called and push data to oButtonSubject.
_subscribeEditPressOb: function (oEditPressed, oButtonSubject) {
let self = this;
return oEditPressed
.map(function (oSource) {
return oSource;
})
.subscribe(function (oSource) {
// Determine select state for the calendar.
// The calendar is only allowed to select, if
// EDIT button is clicked.
if (oSource.getId().indexOf("cal-edit") >= 0 && oSource.getPressed()) {
oButtonSubject.next({bEdit: true, bMass: false, bSave: false, bCancel: false});
} else {
oButtonSubject.next({bEdit: true, bMass: true, bSave: true, bCancel: true});
}
},
function (err) {
jQuery.sap.log.fatal(err);
});
},
The code above subscribe to the edit button when it got pressed, after push the data to the subject.
It is the right approach to use subject?
A Subject is meant as an entrance from the non-reactive world into the Rx world. In your case you are using a Subject to emit events based on click events on the edit button.
I would suggest you do not use a Subject for this specific implementation but use your clicks directly as a stream by using fromEvent
The following is an example (ignore the rough edges; it persists state in the DOM) (jsbin)
const editButton = document.getElementById('enable-edit-mode')
const saveChangesButton = document.getElementById('save-changes');
const cancelChangesButton = document.getElementById('cancel-save-changes');
const enableEditModeStream = Rx.Observable.fromEvent(editButton, 'click')
.do(() => {
editButton.setAttribute('disabled','disabled')
saveChangesButton.removeAttribute('disabled');
cancelChangesButton.removeAttribute('disabled');
});
const saveChangesStream = Rx.Observable.fromEvent(saveChangesButton, 'click')
.do(() => {
console.log('saving changes')
});
const cancelChangesStream = Rx.Observable.fromEvent(cancelChangesButton, 'click');
const saveCancelEditModeStream = Rx.Observable.merge(
saveChangesStream,
cancelChangesStream
)
.do(() => {
editButton.removeAttribute('disabled');
saveChangesButton.setAttribute('disabled','disabled')
cancelChangesButton.setAttribute('disabled','disabled')
});
Rx.Observable.merge(
enableEditModeStream,
saveCancelEditModeStream
)
.subscribe();

Reset timeout on event with RxJS

I'm experimenting with RxJS (with the JQuery extension) and I'm trying to solve the following use case:
Given that I have two buttons (A & B) I'd like to print a message if a certain "secret combination" is clicked within a given timeframe. For example the "secret combination" could be to click "ABBABA" within 5 seconds. If the combination is not entered within 5 seconds a timeout message should be displayed. This is what I currently have:
var secretCombination = "ABBABA";
var buttonA = $("#button-a").clickAsObservable().map(function () { return "A"; });
var buttonB = $("#button-b").clickAsObservable().map(function () { return "B"; });
var bothButtons = Rx.Observable.merge(buttonA, buttonB);
var outputDiv = $("#output");
bothButtons.do(function (buttonName) {
outputDiv.append(buttonName);
}).bufferWithTimeOrCount(5000, 6).map(function (combination) {
return combination.reduce(function (combination, buttonName) {
return combination + buttonName;
}, "");
}).map(function (combination) {
return combination === secretCombination;
}).subscribe(function (successfulCombination) {
if (successfulCombination) {
outputDiv.html("Combination unlocked!");
} else {
outputDiv.html("You're not fast enough, try again!");
}
});
While this works fairly well it's not exactly what I want. I need the bufferWithTimeOrCount to be reset when button A is pressed for the first time in a new timeframe. What I'm looking for is that as soon as the secret combination is pressed (ABBABA) I'd like "Combination unlocked!" to be shown (I don't want to wait for the time window to be expired).
Throttle is the typical operator for the delaying with reactive resetting effect you want.
Here's how you can use throttle in combination with scan to gather the combination inputted before the 5 seconds of silence:
var evaluationStream = bothButtons
.merge(bothButtons.throttle(5000).map(function(){return "reset";})) // (2) and (3)
.scan(function(acc, x) { // (1)
if (x === "reset") return "";
var newAcc = acc + x;
if (newAcc.length > secretCombination.length) {
return newAcc.substr(newAcc.length - secretCombination.length);
}
else {
return newAcc;
}
})
.map(function(combination) {
return combination === secretCombination;
});
var wrongStream = evaluationStream
.throttle(5000)
.filter(function(result) { return result === false; });
var correctStream = evaluationStream
.filter(function(result) { return result === true; });
wrongStream.subscribe(function() {
outputDiv.html("Too slow or wrong!");
});
correctStream.subscribe(function() {
outputDiv.html("Combination unlocked!");
});
(1) We scan to concatenate the input characters. (2) Throttle waits for 5 seconds of event silence and emits the last event before that silence. In other words, it's similar to delay, except it resets the inner timer when a new event is seen on the source Observable. We need to reset the scan's concatenation (1), so we just map the same throttled Observable to "reset" flags (3), which the scan will interpret as clearing the accumulator (acc).
And here's a JSFiddle.

Categories

Resources