Change a function parameter using Javascript - javascript

I use an HTML button to call a function :
<button id="attackButton" type="button" onclick="Enemy.prototype.fight(Test)"> Attaquer </button>
This function is :
Enemy.prototype.fight = function(Enemy)
{
}
This function is linked to these 2 variables :
var Enemy = function (damage, life)
{
this.damage = damage;
this.life= life;
};
var Test= new Enemy (7,20);
I would like to make something which will replace the parameter"Test" by "Test2" within the HTML Button so my button will now redirect to the same function while using a third variable :
var Test2= new Enemy (10,30);
I hope I was clear enough. Thank you. :)
EDIT :
To clarify : A player is fighting different enemies. Enemies have different variables (armor, life, damage etc...) The fight is a function and variables varies based on the parameter. The html button calls that function.
The player is going to make various actions within the game and sometimes he will fight. Sometimes he will not.
I would like to keep one single html button which always call the same function. But the parameter has to change each time I want it.
For instance :
If you take the left side, then you fight Enemy 1.
If you take the right side, then you fight Enemy 2.
Right now the full the full Enemy function is
var Enemy = function (damage, initiative, armor, lifeNow, life)
{
this.damage = damage;
this.initiative = initiative;
this.armor = armor;
this.lifeNow = lifeNow;
this.life = life;
};
and each time I want to add a different enemy I write this :
var enemy1= new Enemy (7, 9, 3, 15, 15);
var enemy2= new Enemy (50, 9, 3, 100, 100);
Hope it helps :p Thank you again

I'm going to have to make a couple of assumptions here. The main one is that you have a number of Enemy objects and you want to attack each one in sequence.
First, use an array, not sequentially named variables.
var enemies = [ new Enemy (7,20), new Enemy (10,30) ];
Second, track which enemy is next to be attacked:
var nextEnemy = 0;
Third, refactor your code to make the attack enemy logic a function of its own.
Attack the current next enemy and then work out which one should be attacked after that.
function attackNextEnemy() {
Enemy.prototype.fight(enemies[nextEnemy]);
nextEnemy++;
if (!enemies[nextEnemy]) {
nextEnemy = 0; // back to the top
}
}
You can then call that function from onclick.
There are various other things you could to that would be better practises (such as mentioned in dfsq's comment, using addEventListener instead of HTML attributes), and putting the nextEnemy variable inside a closure to keep it private, but a Stackoverflow answer isn't the place to go into that level of depth.
After the question was updated:
If you take the left side, then you fight Enemy 1. If you take the right side, then you fight Enemy 2.
In that case, you probably don't want to use an array. You are dealing with positions not sequences.
An object such as:
var enemies = {
left: new Enemy (7,20),
right: new Enemy (10,30)
};
… would make more sense.
Then you would need to change your attack to:
var sideThePlayerIsOn = "left";
and
Enemy.prototype.fight(enemies[sideThePlayerIsOn]);
as well as adding some logic to change the sideThePlayerIsOn variable based on whatever the player can do to change that.

You can extract the variable from the button click event into a kind of context like the game in my example and in this way you can change the enemies any time you want.
For example if the side is changed just call: changeEnemy(game1, enemy2);
var Enemy = function (damage, initiative, armor, lifeNow, life)
{
this.damage = damage;
this.initiative = initiative;
this.armor = armor;
this.lifeNow = lifeNow;
this.life = life;
};
Enemy.prototype.fight = function(enemy) {
alert('it works!' + enemy.damage);
}
var Game = function (enemy1, enemy2)
{
this.enemy1 = enemy1;
this.enemy2= enemy2;
this.fight = function () {
this.enemy1.fight(this.enemy2);
}
};
var enemy1= new Enemy (7, 9, 3, 15, 15);
var enemy2= new Enemy (50, 9, 3, 100, 100);
var enemy3= new Enemy (60, 9, 3, 100, 100);
var game1= new Game(enemy1, enemy2);
function changeEnemy(game, newEnemy) {
game.enemy2 = newEnemy;
}
changeEnemy(game1, enemy3);
<button id="attackButton" type="button" onclick="game1.fight();"> Attaquer </button>

Related

How can I create multiple instances of a class without defining them?

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();

Javascript chaining the same animation function with different parameters

I am trying to animate a line two lines along a path, one then the other. Basically it will look like one line being drawn, stopping at a point, then another line being drawn somewhere else. So far I have come across promises and callbacks to achieve this, but being a javascript newbie this is confusing
Current animate function:
/*
* Animation function draws a line between every point
*/
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length-1){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
Current call to animate function:
animate(points).then(animate(secondary_points));
The points are similar to:
var points = [{x:100, y:200}];
And the paths the lines need to follow are just the multiple coordinates inside points and secondary_points
Ive tried many solutions on SO that were similar, but small differences cause me to either mess up or not understand the solution. The biggest issue I seem to have is calling the SAME animate function, with that animate function needing to be run on different parameters.
Without this solution, using
animate(points);
animate(secondary_points);
the lines are drawn somewhat at the same time, but the result is actually just randomly placed dots along the path instead of smooth lines, I assume because both are running at the same time.
How would I go about fixing this so that one line is drawn along path1 and then the second line is drawn along path2?
It is probably a simple solution, but Ive worked with JS for 3 days and my head is still spinning from getting used to some of the syntax of the old code Ive had to fix
Thank you
EDIT:
The full flow of the animation is as follows:
I have a php file that contains 2 canvases, each containing an image of a map. The php file has a couple <script/> tags, one of which calls the js script I am writing the animation on via drawPath(source,destination,true) or drawPath(source,destination,false)
The drawPath function uses the boolean to determine which canvas to get the context for, and then draw on the path from point A to point B via finding the path and creating the points mentioned above, then drawing using animate(). There are a couple breaks in the maps that require separate lines, which prompted my original question. I was able to fix that thanks to suggestions, but now I am having a larger issue.
If I need to go from point A on map A to point B on map B, ie
drawPath(source, end_point_of_map_A, true); is called then
drawPath(start_point_of_map_B, destination, false);, the lines are drawn only on one map, and they are similar to before where they are 1. random and 2. incomplete/only dots
I am assuming this is due to the animation again, because it worked when just drawing the lines statically, and each animation works when going from point A to B on a single map
Any help is appreciated!
Edit:
DrawPath()
function drawPath(source, desti, flag) {
/*
* Define context
*/
//lower
if(!flag){
var c = document.getElementById("myCanvas");
context = c.getContext("2d");
//upper
} else {
var cUpr = document.getElementById("myCanvasUpr");
context = cUpr.getContext("2d");
}
/*
* Clear the variables
*/
points = [];
secondary_points = [];
vertices = [];
secondary_vertices = [];
t = 1;
done = false;
//check for invalid locations
if (source != "" && desti != "") {
context.lineCap = 'round';
context.beginPath();
/*
* Get the coordinates from source and destination strings
*/
var src = dict[source];
var dst = dict[desti];
/*
* Get the point number of the point on the path that the source and destination connect to
*/
var begin = point_num[source];
var finish = point_num[desti];
/*
* Draw the green and red starting/ending circles (green is start, red is end)
*/
context.beginPath();
context.arc(src[0], src[1], 8, 0, 2 * Math.PI);
context.fillStyle = 'green';
context.fill();
context.beginPath();
context.arc(dst[0], dst[1], 6, 0, 2 * Math.PI);
context.fillStyle = 'red';
context.fill();
/*
* Call the function that draws the entire path
*/
draw_segments(begin, finish, src, dst, flag);
//window.alert(JSON.stringify(vertices, null, 4))
/*
* Edit what the line looks like
*/
context.lineWidth = 5;
context.strokeStyle = "#ff0000";
context.stroke();
}
}
A nice way to handle this is to put your lines into a an array where each element is a set of points of the line. Then you can call reduce() on that triggering each promise in turn. reduce() takes a little getting used to if you're new to javascript, but it basically takes each element of the array c in this case, does something and that something becomes the next a. You start the whole thing off with a resolve promise which will be the initial a. The promise chain will be returned by reduce to you can tack on a final then to know when the whole thing is finished.
For example:
let canvas = document.getElementById('canvas')
let context = canvas.getContext('2d');
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length-1){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
// make some points:
let points = Array.from({length: 200}, (_,i) => ({x:i+1, y:i+2}))
let points2 = Array.from({length: 200}, (_,i) => ({x:300-i, y:i+2}))
let points3 = Array.from({length: 200}, (_,i) => ({x:i*2, y:100+100*Math.sin(i/10)}))
// create an array holding each set
let sets = [points, points2, points3]
// use reduce to call each in sequence returning the promise each time
sets.reduce((a, c) => a.then(() => animate(c)), Promise.resolve())
.then(() => console.log("done"))
<canvas id="canvas" height="300" width="500"></canvas>

Creating objects and storing them in an array

I'm very new to Javascript, and what I'm about to ask is probably very rudimentary, but I'm stuck on a project, and I'd like some help.
Basically I'm doing a small Javascript project, where I want there to be some enemies. In the beginning there will be 0 enemies, and then as time goes on, there might be created more enemies. I was thinking that whenever an enemy is created, I should create it as an object, and then store it in an array containing all the active enemies in game. Then, later on, I could remove enemies from the array, that needed to be removed.
This is the function that creates my enemy objects:
function enemy(name, strength, rarity, estTime, success, remTime, Id){
this.name = name;
this.strength = strength;
this.rarity = rarity;
this.estTime = estTime;
this.success = success;
this.remTime = remTime;
this.Id = Id;
}
So, I could then create some enemies like this:
var enemy1 = new enemy("Bob", 1, "Common", 100, 1, 30, 1)
var enemy2 = new enemy("Cow", 22, "Rare", 50, 10, 40, 2)
var enemy3 = new enemy("Pig", 333, "Epic", 25, 10, 50, 3)
Then I could create an array of enemies, and put my 3 enemies into that array:
var enemies = [];
enemies = [enemy1, enemy2, enemy3];
All fine and dandy when I'm doing this manually, but the problem emerges when I want to try to get the code to automatically create some more enemies.
Say I wanted to create an enemy everytime a button was pushed by the user. The name enemy would get some name, strength, rarity whatever, and the next Id in line (in this case 1, 2 and 3 are being used, so the next would be 4).
I was thinking I could do it something like this, but this doesn't work:
enemy[enemies.length + 1] = new enemy("Dog", 444, "Epic", 13, 100, 60, 4);
enemies.push(enemy + [enemies.length + 1]);
I was hoping that this would create an object called "enemy4" with with name, id and whatever I just typed in, and then add that object to the array of enemies.
But this obviously doesn't work. I hope you guys understand the problem, and any help is greatly appreciated. I realize that I'm probably just approaching this all wrong, and that there probably exists a much simpler way to do this.
Thanks!
EDIT: Yep, answer was very simple, got it now. Thanks!
var enemies = [
new enemy(/*params here*/),
new enemy(/*params here*/),
new enemy(/*params here*/),
new enemy(/*params here*/),
new enemy(/*params here*/)
];
Then later:
enemies.push(
new enemy(/*params here*/)
);

RaphaelJS / CSS - Animating text/numbers

So, I am just diving into simple web animations for a game, and I am looking for advice. Eventually, I'll get a good grip on beziers and arcs and learn how to animate along a path to get some nice Diablo III-esque curving numbers but, for now, I am just trying to get the fundamentals down.
First (real) attempt
The key code is pretty simple-
paper.text(170, 95, dmgValue).attr({fill:"white", "font-size":16}).animate({
transform:"t0,-50", "fill-opacity":0} ,500).node.setAttribute("class", "no-select");
A CSS styling prevents the text from being highlighted (thanks to a user here for the help). The main issue, is that the text is still there with no opacity- you can hover over it and see the text cursor. Although it works, it' kind of messy looking. Also, since there is no variable assigned, I don't think I can dispose of it with Element.remove();
Where I am at now
There were a lot of small revisions I made in-between saved versions that made the code to the bulkiness that it is now. I wanted the ability to limit the number of numbers flying around at once (for slower computers), so I put them into an array that can be looped endlessly and used, although that probably isn't needed and it wouldn't be a big deal to leave it out.
Also moved from using transform, to setting the y-coords, and placing the .hide() into a separate function for the callback (which, for some reason worked instead of placing it at the end of the animation).
This version appears to work at first, but the animations get interrupted when you click too many times and I'm not sure why. I am sure I can figure it out in the end with enough time, but I might be making this too complicated, anyway. The full code-
var paper = Raphael(0, 0, 350, 350);
paper.canvas.style.backgroundColor = "Black";
var dmgValues = [],
dmgValuesIndex = 0,
maxMsgs = 15,
dmgXMaxOffset = 25,
dmgYMaxOffset = 25,
dmgXRef = 170 - dmgXMaxOffset,
dmgYRef = 250 - dmgYMaxOffset,
dmgMaxDistance = 50;
for (i=0; i< maxMsgs; i++) {
dmgValues[i] = paper.text().attr({fill:"white", "font-size":16});
dmgValues[i].node.setAttribute("class", "no-select");
dmgValues[i].hide();
}
var toggle = paper.rect(150, 270, 50, 25).attr({fill:"green"});
toggle.click(function() { doHit(); });
function doHit() {
var dmgHit = Math.floor(Math.random() * 99) + 1,
xPos = Math.floor(Math.random() * dmgXMaxOffset) + 1,
yPos = Math.floor(Math.random() * dmgYMaxOffset) + 1;
dmgValues[dmgValuesIndex].show();
if (dmgValues[dmgValuesIndex].status() == 1) { dmgValues[dmgValuesIndex].stop(); }
dmgValues[dmgValuesIndex].attr({x:dmgXRef + xPos, y:dmgYRef + yPos, text:dmgHit,
"fill-opacity":1}).animate({y:dmgYRef - dmgMaxDistance, "fill-opacity":0}, 600,
"linear", function() { afterEffects(dmgValues[dmgValuesIndex]) });
}
function afterEffects (afterTarget) {
afterTarget.hide();
dmgValuesIndex++;
if (dmgValuesIndex >= maxMsgs) { dmgValuesIndex = 0; }
}
CSS:
.no-select {
-moz-user-select: none;
-webkit-user-select: none;
}
I think I figured it out!
http://jsfiddle.net/rLcwax9k/10/
One thing I noticed was that that the incrementer was in the callback function that occurred after the animation, so it wasn't really counting right. But, mainly, because the dmgValuesIndex was global, and was getting incremented on each click. So, by the time the animation was done, it was doing functions based on whatever the current count was at the end of the animation in the callback, which may not have been the right one. So, I just put a parameter on the function and used that as the reference throughout the call and passed it to the callback.
Heh, I am sort of beginning to see why a lot of languages need setter and getter methods on their objects. This should be a good lesson to noobs like me on operating with global variable scope and their possible side-effects.
However, before I accept an answer, I am still looking for any other methods that may be more efficient.
Main code-
function doHit(iter) {
this.iter = iter;
var dmgHit = Math.floor(Math.random() * 99) + 1,
xPos = Math.floor(Math.random() * dmgXMaxOffset) + 1,
yPos = Math.floor(Math.random() * dmgYMaxOffset) + 1;
dmgValues[iter].show();
if (dmgValues[iter].status() == 1) { dmgValues[iter].stop(); }
dmgValues[iter].attr({
x:dmgXRef + xPos,
y:dmgYRef + yPos,
text:dmgHit,
"fill-opacity":1
})
.animate({
y:(dmgYRef + yPos) - dmgMaxDistance,
"fill-opacity":0},
1000,
">",
function() {
dmgValues[iter].hide();
}
);
}

ThreeJS - how to set current time in Animation

I'm using skinning / skeletal animation in ThreeJS. I have an animation, and I want to be able to move backward and forward through it, and jump to different locations within it, rather than the usual looping behaviour.
The animation is created like this, as in the example:
var animation = new THREE.Animation( mesh, geometry.animation.name );
I have tried updating the animation with negative deltas, as well as setting animation.currentTime directly:
animation.currentTime = animationLocation;
These appear to work only if I move forward in time, but if I go backward the animation breaks and I get an error:
THREE.Animation.update: Warning! Scale out of bounds: ... on bone ...
One thing that does actually work without error is to call stop() and then play() with a new start time:
animation.stop();
animation.play( true, animationLocation );
...however when I look at what these functions are actually doing, they involve many many function calls, looping, resetting transforms etc. This seems like a horrible way to do it, even if it works as a hack.
It may be that this functionality does not exist yet, in which case I'll try to dig in and create a function that does a minimal amount of work, but I'm hoping there is another way that I haven't found.
Can anyone help with this?
[UPDATE]
As an update on my progress, I'll post the best solution I have at this time...
I pulled out the contents of the stop() and play() functions, and stripped out everything I could, making some assumptions about certain values having already been set by 'play()'.
This still seems like it is probably not the best way to do it, but it is doing a bit less work than by just calling stop() then play().
This is what I was able to get it down to:
THREE.Animation.prototype.gotoTime = function( time ) {
//clamp to duration of the animation:
time = THREE.Math.clamp( time, 0, this.length );
this.currentTime = time;
// reset key cache
var h, hl = this.hierarchy.length,
object;
for ( h = 0; h < hl; h ++ ) {
object = this.hierarchy[ h ];
var prevKey = object.animationCache.prevKey;
var nextKey = object.animationCache.nextKey;
prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ];
prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ];
prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ];
nextKey.pos = this.getNextKeyWith( "pos", h, 1 );
nextKey.rot = this.getNextKeyWith( "rot", h, 1 );
nextKey.scl = this.getNextKeyWith( "scl", h, 1 );
}
//isPlaying must be true for update to work due to "early out"
//so remember the current play state:
var wasPlaying = this.isPlaying;
this.isPlaying = true;
//update with a delta time of zero:
this.update( 0 );
//reset the play state:
this.isPlaying = wasPlaying;
}
The main limitation of the function in terms of usefulness is that you can't interpolate from one arbitrary time to another. You can basically just scrub around in the animation.
You can use THREE.Clock and assign startTime, oldTime, elapsedTime.

Categories

Resources