I've got what seems like a very simple problem but I'm having some problems fixing it. I'm trying to create an interactive cat object on my page with Javascript that basically performs the following expected behavior:
When the page first loads, the cat is laying sleeping in the middle of the screen (cat-sleeping.gif)
When you move the pointer (customized to look like a mouse) a little bit closer to the cat, it sits up and starts grooming itself (cat-sitting.gif)
When you move the pointer even closer, the cat starts chasing after the cursor (cat-chasing.gif)
To handle detecting the mouse proximity and the chase behaviour, I'm using a loop that's being called from window.requestAnimationFrame after an initial setup call. The chase + mouse proximity are working as expected, but the changing animation states are not. Although the image changes as expected when the cat is activated, the animated gif becomes a static image. If it matters, I'm using Google Chrome to test things out.
Here's a snippet of the loop (note this function isn't completely finished, but everything that should be working so far works, except the animation)
function loop() {
const { pointerEvent, pointerStatic, catActive } = state;
const { cat, cursor } = settings;
// Calculate distance between cat and pointer
const distance = Math.sqrt(
Math.pow(pointerEvent.x - cat.element.offsetLeft, 2) +
Math.pow(pointerEvent.y - cat.element.offsetTop, 2)
);
// Normalize the distance
const distanceNorm = scale(distance, 0, 1000);
// Activate the cat after the pointer gets close enough
if (distanceNorm < 0.1) {
updateState({ catActive: true });
}
// Flip the cursor left or right depending on the direction of movement
if (pointerEvent.movementX < 0) {
cursor.element.style.transform = "scaleX(-1)";
} else {
cursor.element.style.transform = "scaleX(1)";
}
// Make the cat turn from left to right when the pointer is moving
if (pointerEvent.x < cat.element.offsetLeft + cat.width / 2) {
cat.element.style.transform = "scaleX(-1)";
} else {
cat.element.style.transform = "scaleX(1)";
}
// If the cat is active, chase the pointer
if (catActive) {
cat.element.src = "cat-walk.gif";
cat.element.style.left = `${toAbsolute(
distanceNorm,
cat.element.offsetLeft,
pointerEvent.x - 80
)}px`;
cat.element.style.top = `${toAbsolute(
distanceNorm,
cat.element.offsetTop,
pointerEvent.y - 35
)}px`;
}
window.requestAnimationFrame(loop);
}
Changing my state function fixed this - I made a separate change state function specifically for the cat, since I realized that state was otherwise being updated every frame as the pointer moved due to sharing data with the PointerElement event. However, I'm not 100% sure why this actually worked - to make it function, I needed to directly access catActive from my catState object instead of using its destructured variant. I'm posting this as an answer so that I can share the code, but I'd like to understand why this actually works so that I learn instead of surviving by trial and error!
function updateCatState(newState) {
let { catActive, cat, catForm } = catState;
catState = { ...catState, ...newState };
if (catState.catActive && cat.element.src !== catForm[1]) {
console.log("catActive");
cat.element.src = catForm[1];
} else {
cat.element.src = catForm[0];
}
}
Related
I am trying to implement a stats table into my game.
Currently it's very buggy: the streak works when the if statement is true, but if I get the words correct once, the streak goes up and if I get the words correct again the streak goes up. When the statement is false the streak goes back to 0 (which I want to happen).
The problem arises when I get the words correct again, the streak does not increment anymore.
Also the win percentage sometimes works and sometimes the calculations are incorrect. Have I set the function up incorrectly?
if (guessedLetters.length == figureGuessWordsLength) {
LSdataWordle.CurrrentStreak++;
if (LSdataWordle.CurrrentStreak >= LSdataWordle.MaxStreak) {
LSdataWordle.MaxStreak = LSdataWordle.CurrrentStreak;
}
LSdataWordle.WinPercentage = (
(parseFloat(LSdataWordle.MaxStreak) /
parseFloat(LSdataWordle.DaysPlayed)) *
100
).toFixed(2);
window.localStorage.setItem("dataWordle", JSON.stringify(LSdataWordle));
} else {
LSdataWordle.CurrrentStreak = 0;
window.localStorage.setItem("dataWordle", JSON.stringify(LSdataWordle));
}
}
One thing I am seeing is that you aren't recalculating the win percentage on a loss. That would be why sometimes it probably doesn't make sense for you. So take that completely out of the if statements like below, as well as the window.localstorage call seems to be independent of any conditions.
As to figure out why it won't increment on a win after losing streak, that will come down to what guessedLetters.length is and what figureGuessWordsLength is. put a breakpoint and see if they even match on a win, because the way you posted your code is as if that is a for-sure thing, but it may not be.
if (guessedLetters.length == figureGuessWordsLength) {
LSdataWordle.CurrrentStreak++;
if (LSdataWordle.CurrrentStreak >= LSdataWordle.MaxStreak) {
LSdataWordle.MaxStreak = LSdataWordle.CurrrentStreak;
}
}
else {
LSdataWordle.CurrrentStreak = 0;
}
LSdataWordle.WinPercentage = (
(parseFloat(LSdataWordle.MaxStreak) /
parseFloat(LSdataWordle.DaysPlayed)) *
100
).toFixed(2);
window.localStorage.setItem("dataWordle", JSON.stringify(LSdataWordle));
So, I'm making a game on HTML5 canvas. It's a top down shooter, and I need to create a bullet every time you click to make the character shoot.
Initially, I just prevented the player from firing another bullet until it went out of bounds or it hit an enemy, as seen here. This worked percetly, but of course, makes for uninteresting gameplay.
Then, I began researching about JS classes, and I thought that it would be the key to the problem. I created a bullet class, and moved all the logic for the bullet to the class. Then, I created an instance of it, and called it in other parts of the code to execute its logic. This worked exactly as it did before, which is good, because it meant I could translate the thing I had before to a class, but it had a similar issue.
This is how the class is defined:
class bullet{
constructor(_img, _piercing){
this.bulletPic = document.createElement("img");
this.img = this.bulletPic.src = _img;
this.piercing = _piercing;
}
shoot(){
this.bulletAngle = playerAngle;
this.bulletX = playerX;
this.bulletY = playerY;
bulletShot = true;
shots = 0;
}
draw(){
canvasContext.save();
canvasContext.translate(this.bulletX, this.bulletY);
canvasContext.rotate(this.bulletAngle);
canvasContext.drawImage(this.bulletPic, -this.bulletPic.width / 2, -this.bulletPic.height / 2);
canvasContext.restore();
if(bulletShot){
this.bulletX += Math.sin(this.bulletAngle) * BULLET_SPEED;
this.bulletY -= Math.cos(this.bulletAngle) * BULLET_SPEED;
}
}
}
And here is the object definition:
let bullet1 = new bullet("Textures/player.png", true);
If I want to shoot another bullet at the same time, I need to have already defined a new instance of the bullet class, is there any way for me to define a new instance every time I click?
Edit: The shoot and draw methods are called in another file that follow logic that's not shown here. Mainly what this other code does, is detect when it hits an enemy or when it goes out of bounds to set "bulletShot" to false, that makes it "despawn", and I can shoot another bullet. This is part of the 1 bullet at a time limitation I'm trying to remove here, but that can go once this central issue is fixed.
If I understand your situation, you could use a function that returns a new class:
function bulletFactory( className ) {
return new className();
}
If you want to achieve that there could be several bullets in "mid-air", after a series of fast consecutive clicks, then create an array of bullets. You would initialise that array like this:
const bullets = Array({length: ammo}, () => new Bullet());
ammo would be the number of bullets that the user can shoot in total.
NB: I simplified the call of the constructor. Add the arguments you want to pass. Secondly, it is common practice to start class names with a capital.
Then add a state property in the Bullet instances that indicates whether the bullet is:
Hidden: it is not visible yet, but part of the total ammunition that can still be used in the future
Ready: it is the one bullet that is visible at the start location, ready to be fired by the user
Shot: a bullet that has been shot and is currently flying through the game area
At first this state is "hidden":
constructor(_img, _piercing){
this.state = "hidden";
// ...
}
draw() {
if (this.state === "hidden") return; // Don't draw bullets that are not available
// ...
}
Then at the start of the game, make one bullet visible (where it should be clicked):
bullets[0].state = "ready"; // From now on it will be drawn when `draw()` is called
In the click handler do the following:
// Fire the bullet the user clicked on:
bullets.find(bullet => bullet.state === "ready").shoot(playerAngle, playerX, playerY);
// See if there is a next bullet remaining in the user's ammo:
const nextBullet = bullets.find(bullet => bullet.state === "hidden");
if (nextBullet) nextBullet.state = "ready"; // Otherwise ammo is depleted.
The shoot method should not rely on global variables, but get the necessary external info as arguments:
shoot(playerAngle, playerX, playerY) {
this.bulletAngle = playerAngle;
this.bulletX = playerX;
this.bulletY = playerY;
this.state = "shot";
}
Don't use global variables inside your class methods (shot, ammo,...). Instead use arguments or other instance properties.
The draw method should also work with that state:
draw() {
if (this.state === "hidden") return; // Don't draw bullets that are not available
// ...
if(this.state === "shot") {
this.bulletX += Math.sin(this.bulletAngle) * BULLET_SPEED;
this.bulletY -= Math.cos(this.bulletAngle) * BULLET_SPEED;
}
}
In your animation loop, you should call draw on all bullets. Something like:
bullets.forEach(bullet => bullet.draw());
I did not see any code for when a bullet has left the game area, either by hitting something or just flying out of range. In such case the bullet should be removed from the bullets array to avoid that the draw method keeps drawing things without (visual) significance.
Here is how you could delete a specific bullet:
function deleteBullet(bullet) {
const i = bullets.indexOf(bullet);
if (i > -1) bullets.splice(i, 1);
}
I hope this gets you going on your project.
I ended up making an array that contains multiple instances of the class. I defined a variable that I used as a limit and then set up a for statement to create all the objects, then, I can call them using the array name and the position.
for(var i = 0; i < arraySize; i++){
arrayName[i] = new className(parameters);
}
Examples of usage:
arrayName[5].method();
How to effectively create curveVertex with an array of p5.Vector points which fades (decreases opacity) the trail after the mouse position?
So far the following code removes the trailing points after a certain amount of time, creating the effect of trailing after the mouse, but it does not create a translucent trail. The trailing stroke just goes away.
const trail = sk => {
let points = [];
let fadeTime = 1000;
sk.setup = () => {
sk.createCanvas(300, 600);
sk.noFill();
};
sk.draw = () => {
sk.clear();
sk.beginShape();
for (let i = points.length - 1; i >= 0; i--) {
let p = points[i];
let timeAlive = sk.millis() - p.z;
if (timeAlive > fadeTime) {
// start removing last point
points.shift();
} else {
sk.strokeWeight(10);
sk.strokeJoin(sk.ROUND);
sk.strokeCap(sk.ROUND);
sk.stroke(255, 255, 255);
sk.curveVertex(p.x, p.y);
}
}
sk.endShape();
}
// Add more points forward when dragging mouse
sk.touchMoved = () => {
points.push(sk.createVector(sk.mouseX, sk.mouseY, sk.millis()));
return false;
}
};
let mySketch = new p5(trail, elementId);
The problem is that shapes created using the beginShape() and vertex functions (including curveVertex()) can only have a single stroke and fill color. So you can't color parts of the shape differently from the rest of the shape.
To prove this, try changing this line:
sk.stroke(255, 255, 255);
To this line:
sk.stroke(random(256));
To get around this, you could make each section of your curve its own shape with its own color. Start by just making each section a random color.
Then you need to base the color of each section off of the current index. I recommend drawing out a few examples. If a curve has 10 sections, what color should section 1 have? Section 2? Section 10? Repeat that process until you notice a pattern.
If you get stuck, please post an updated MCVE in a new question, and we'll go from there. Good luck.
I'm develop my first phaser game for a client. The game is a car moving forward and it have two minutes to get the goal.
I want to increase progressive the car speed while the Up key is pressed until the speed limit has reached.
I'm moving the racetrack not the car by doing:
this.highway2 = game.add.tileSprite(game.world.centerX,game.world.height/2, game.cache.getImage('highway').width, game.cache.getImage('highway').height, 'highway');
this.highway2.anchor.setTo(0.5,0.5);
this.highway2.autoScroll(0,1000);
So, my questions are:
how can I control the speed of the autoScroll to simulate acceleration?
Is there a way to know how much time was a key pressed?
Is this the right approach to get this done?
Thanks in advanced.
Well, I don't know if this is the better way to do this, but it's work pretty well.
Just set speed limit and track it in the update function.
var playState = {
create: function(){
this.setInitialValues();
game.physics.startSystem(Phaser.Physics.ARCADE);
this.cursor = game.input.keyboard.createCursorKeys();
//highway
this.highway2 = game.add.tileSprite(game.world.centerX,game.world.height/2, game.cache.getImage('highway').width, game.cache.getImage('highway').height, 'highway');
this.highway2.anchor.setTo(0.5,0.5);
this.highway2.autoScroll(0,0);
//car
this.player = game.add.sprite(game.world.centerX+10, game.world.height-150, 'player');
this.player.anchor.setTo(0.5,0.5);
game.physics.arcade.enable(this.player);
//other things
},
update: function(){
this.movePlayer();
},
movePlayer: function(){
// move left and right
// If the left arrow key is pressed
if (this.cursor.left.isDown)
{
// Move the player to the left
this.player.body.velocity.x = -200;
}
// If the right arrow key is pressed
else if (this.cursor.right.isDown)
{ // Move the player to the right
this.player.body.velocity.x = 200;
}
// If neither the right or left arrow key is pressed
else
{
// Stop the player
this.player.body.velocity.x = 0;
}
//speed up and speed down
if (this.cursor.up.isDown)
{
if(this.currentSpeed < this.maxSpeed )
{
this.currentSpeed+=10;
this.highway2.autoScroll(0,this.currentSpeed);
}
}
else{
if(this.currentSpeed > 0 )
{
this.currentSpeed-=10;
this.highway2.autoScroll(0,this.currentSpeed);
}
}
if (this.cursor.down.isDown)
{
if(this.currentSpeed > 0 )
{
this.currentSpeed-=30;
this.highway2.autoScroll(0,this.currentSpeed);
}
}
},
setInitialValues: function(){
this.maxSpeed=1500;
this.currentSpeed=0;
}
}
You should apply a strict separation of concerns to 1) simplify coding 2) ease the fine tuning and even make the game more interesting and 3) easily 'plug' you logic with another controller (touch event instead of keyboard).
So here you have two separate concerns :
* measure for how long the user has been accelerating.
* given current speed, min/max speed, thrust time, decide what is current acceleration (== speed change).
For 1) it's pretty straightForward : record the time when input start, now duration is current time - start time. You'd better use game time if you have one (rather than Date.now()), so that you avoid surprises after the game resumes from a long tab-out.
For 2) You should fine tune the acceleration/deceleration of your game, it will make it more interesting.
Most obvious is not to have a constant acceleration : it must be harder and harder to reach the last % of the max speed. This way you give and incentive/reward to the player not to touch obstacle.
What you should do if the player is not thrusting, i don't know : slowly decelerate or quickly return to normal speed ?
Also you'll have to decide wether the boost is infinite or not, and maybe of a cool-down time.
So the function that compute current acceleration depends on thrusting (bool), thrust time (double) regularSpeed, maxSpeed, minAcc, maxAcc.
There are a lot of options here, but the code to compute acceleration could look like :
if (thrusting) {
// if we're not even at regular speed, max acceleration
if (speed<regularSpeed) { acc = maxAcc; return; }
// if we are above maxSpeed, no acceleration (?? or friction ??)
if (speed>maxSpeed) { acc=0; return; }
// compute current speed ratio
// a figure in [0;1] representing where is the speed in [minSpeed; maxSpeed]
var speedRatio = (currSpeed-regularSpeed)/(maxSpeed-regularSpeed);
// ease this ratio as you like
speedRatio = Math.sqrt(speedRatio);
// compute acceleration : the more speed, the less acceleration
// you might want to put one/some threshold this formula.
acc= minAcc + (1-speedRatio)*(maxAcc-minAcc);
return;
} else {
// do nothing if <= regularSpeed.
if (speed<=regularSpeed) { acc=0 ; return;}
// reduce speed if above regular speed
acc = breakAcc ; // or with friction => acc = - k * currSpeed;
return;
}
I'm trying to trigger an event half-way through the progress (not time) of a transition. It sounds simple, but since the transition can have any curve it's quite tricky. In my particular case it's not going to be paused or anything so that consideration is out of the way.
(Simplified) essentially I could trigger an animation on a modifier like this:
function scaleModifierTo(stateModifier, scale, animationDuration) {
stateModifier.setTransform(
Transform.scale(scale, scale, scale),
{
duration: animationDuration,
curve: this.options.curve
}
);
}
When the interpolated state of the Transitionable hits 0.5 (half-way through) I want to trigger a function.
I haven't dug that deep behind in the source of famo.us yet, but maybe need to do something like
subclass something and add the possibility to listen when the state passes through a certain point?
reverse the curve defined and use a setTimeout (or try to find a proximity using a few iterations of the chosen curve algorithm (ew))
Is it possible to do this easily? What route should I go down?
I can think of a couple of ways to achieve such, and both lend to the use of Modifier over StateModifier. If you are new, and haven't really had the chance to explore the differences, Modifier consumes state from the transformFrom method which takes a function that returns a transform. This is where we can use our own Transitionable to supply state over the lifetime of our modifier.
To achieve what you wish, I used a Modifier with a basic transformFrom that will alter the X position of the surface based on the value of the Transitionable. I can then monitor the transitionable to determine when it is closest, or in my case greater than or equal to half of the final value. The prerender function will be called and checked on every tick of the engine, and is unbinded when we hit the target.
Here is that example..
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var Modifier = require('famous/core/Modifier');
var Transform = require('famous/core/Transform');
var Transitionable = require('famous/transitions/Transitionable');
var SnapTransition = require('famous/transitions/SnapTransition');
Transitionable.registerMethod('snap',SnapTransition);
var snap = { method:'snap', period:1000, damping:0.6};
var context = Engine.createContext();
var surface = new Surface({
size:[200,200],
properties:{
backgroundColor:'green'
}
});
surface.trans = new Transitionable(0);
surface.mod = new Modifier();
surface.mod.transformFrom(function(){
return Transform.translate(surface.trans.get(),0,0);
});
context.add(surface.mod).add(surface);
function triggerTransform(newValue, transition) {
var prerender = function(){
var pos = surface.trans.get();
if (pos >= (newValue / 2.0)) {
// Do Something.. Emit event etc..
console.log("Hello at position: "+pos);
Engine.removeListener('prerender',prerender);
}
}
Engine.on('prerender',prerender);
surface.trans.halt();
surface.trans.set(newValue,transition);
}
surface.on('click',function(){ triggerTransform(400, snap); });
The downside of this example is the fact that you are querying the transitionable twice. An alternative is to add your transitionable check right in the transformFrom method. This could get a bit strange, but essentially we are modifying our transformFrom method until we hit our target value, then we revert back to the original transformFrom method.. triggerTransform would be defined as follows..
Hope this helps!
function triggerTransform(newValue, transition) {
surface.mod.transformFrom(function(){
pos = surface.trans.get()
if (pos >= newValue/2.0) {
// Do something
console.log("Hello from position: " + pos)
surface.mod.transformFrom(function(){
return Transform.translate(surface.trans.get(),0,0);
});
}
return Transform.translate(pos,0,0)
})
surface.trans.set(newValue,transition);
}
Thank you for your responses, especially #johntraver for the prerender event, I wasn't aware of the existence of that event.
I realised it made more sense that I should handle this logic together with my move animation, not the scale one. Then, I ended up using a (very hacky) way of accessing the current state of the transition and by defining a threshold in px I can trigger my function when needed.
/**
* Move view at index to a specified offset
* #param {Number} index
* #param {Number} xOffset xOffset to move to
* #param {Object} animation Animation properties
* #return void
*/
function moveView(index, xOffset, animation) {
var rectModifier = this._views[index].modifiers.rect;
var baseXOffset = rectModifier._transformState.translate.state[0];
// After how long movement is reflow needed?
// for the sake of this example I use half the distance of the animation
var moveThreshold = Math.abs(baseXOffset - xOffset)/2;
/**
* Callback function triggered on each animation frame to see if the view is now covering
* the opposite so we can trigger a reflow of the z index
* #return void
*/
var prerender = function() {
var numPixelsMoved = Math.abs(baseXOffset - rectModifier._transformState.translate.state[0]);
if (numPixelsMoved > moveThreshold) {
Engine.removeListener('prerender', prerender);
// trigger a method when this is reached
_reflowZIndex.call(this);
}
}.bind(this);
rectModifier.setTransform(
Transform.translate(xOffset, 0, 0),
animation,
function() {
Engine.removeListener('prerender', prerender);
}
);
Engine.on('prerender', prerender);
}
Obviously the ._transformState.translate.state[0] is a complete hack, but I couldn't figure out of getting this value in a clean way without adding my own Transitionable, which I don't want. If there is a cleaner way of finding the current state as a Number between 0.0-1.0 that would be ace; anyone knows of one?