I have two classes, one of which is a subclass of the other. What I want to do is have one of the functions, step, invoked when the subclass is instantiated. This will cause the subclass to blink. I can see how to do it with functional instantiation but not with pseudoclassical. I've attached the jquery and javascript. Basically, when the button is clicked, I want the blinky dancer to blink, this should happen via the step function. Maybe I'm calling it incorrectly in the dancer superclass?
$(document).ready(function() {
window.dancers = [];
$('.addDancerButton').on('click', function(event) {
var dancerMakerFunctionName = $(this).data('dancer-maker-function-name');
console.log(dancerMakerFunctionName);
var dancerMakerFunction = window[dancerMakerFunctionName];
var dancer = new dancerMakerFunction(
$("body").height() * Math.random(),
$("body").width() * Math.random(),
Math.random() * 1000
);
$('body').append(dancer.$node);
});
});
var makeBlinkyDancer = function(top, left, timeBetweenSteps) {
makeDancer.call(this, top, left, timeBetweenSteps);
};
makeBlinkyDancer.prototype = Object.create(makeDancer.prototype);
makeBlinkyDancer.prototype.constructor = makeBlinkyDancer;
var oldStep = makeBlinkyDancer.prototype.step;
makeBlinkyDancer.prototype.step = function() {
oldStep();
this.$node.toggle();
};
var makeDancer = function(top, left, timeBetweenSteps) {
this.$node = $('<span class="dancer"></span>');
};
makeDancer.prototype.step = function() {
setTimeout(step.bind(this), timeBetweenSteps);
};
makeDancer.prototype.setPosition = function (top, left) {
var styleSettings = {
top: top,
left: left
};
this.$node.css(styleSettings);
};
// makeDancer.prototype.setPosition();
You can call the method when the subclass is instantiated by calling init in the constructor. In the example and demo I called this method makeBlink rather than step or oldStep. Hope that makes it clear.
// the subclass BlinkyDancer
var BlinkyDancer = function (top, left, height, width, timeBetweenSteps) {
// ... get props from parent class
// make blink when sublcass instantiated
this.init = this.makeBlink();
};
Full Demo/Solution
Demo: https://codesandbox.io/s/call-method-when-class-instantiated-mnesxt
HTML
<body>
<div id="app">
<button id="make-blink">Make Blink</button>
<button id="stop-blink">Stop Blink</button>
</div>
</body>
JS
import $ from "jquery";
var Dancer = function (top, left, height, width) {
this.$node = $('<div class="dancer"></div>');
this.top = top;
this.left = left;
this.height = height;
this.width = width;
this.bg = "blue";
};
Dancer.prototype.putDancerOnScreen = function () {
this.$node.height(this.height).width(this.width);
this.$node.css("background-color", "blue");
this.$node.css("position", "absolute");
$("body").append(this.$node);
};
Dancer.prototype.setPosition = function () {
this.$node.css("top", this.top);
this.$node.css("left", this.left);
};
var myDancer = new Dancer(
Math.floor(Math.random() * 500),
Math.floor(Math.random() * 500),
50,
50
);
myDancer.putDancerOnScreen();
myDancer.setPosition();
// the subclass BlinkyDancer
var BlinkyDancer = function (top, left, height, width, timeBetweenSteps) {
Dancer.call(this, top, left, height, width); // get the props set up by Dancer
this.timeBetweenSteps = timeBetweenSteps;
this.blinkerId = null;
// make blink when sublcass instantiated
this.init = this.makeBlink();
};
// set inheritance and Dancer constructor function
BlinkyDancer.prototype = Object.create(Dancer.prototype);
BlinkyDancer.prototype.constructor = Dancer;
// add subclass methods
BlinkyDancer.prototype.makeBlink = function () {
if (this.blinkerId !== null) {
return;
}
let count = 0;
let blinkerId = setInterval(() => {
// do whatever thing you want to indicate blinking/dancing
if (count % 2 === 0) {
this.$node.css("background-color", "red");
} else {
this.$node.css("background-color", "blue");
}
count++;
}, this.timeBetweenSteps);
this.blinkerId = blinkerId;
console.log("blinkder id set: ", this.blinkerId);
};
BlinkyDancer.prototype.stopBlink = function () {
if (this.blinkerId === null) {
// already blinking
return;
}
if (this.blinkerId !== null) {
clearInterval(this.blinkerId);
console.log("blink id cleared: ", this.blinkerId);
this.blinkerId = null;
}
};
// instantiate a new subclass
let myBlinkyDancer = new BlinkyDancer(
Math.floor(Math.random() * 500),
Math.floor(Math.random() * 500),
50,
50,
25
);
// use parent class methods to put the element on screen
myBlinkyDancer.putDancerOnScreen();
myBlinkyDancer.setPosition();
const makeBlinkButton = document.getElementById("make-blink");
const stopBlinkButton = document.getElementById("stop-blink");
makeBlinkButton.addEventListener("click", function () {
myBlinkyDancer.makeBlink();
});
stopBlinkButton.addEventListener("click", function () {
myBlinkyDancer.stopBlink();
});
Explanation
The Dancer class takes some props related to positioning and dimensions. It gets a blue background and has two methods for adding the element to the DOM and setting the position on screen.
BlinkyDancer is a subclass that inherits all the props of Dancer as well as two new props and two new methods.
What I want to do is have one of the functions, step, invoked when the
subclass is instantiated.
When a new BlinkyDancer is instantiated, we call makeBlink right away with init so the element starts blinking.
// make blink when sublcass instantiated
this.init = this.makeBlink();
I used an alternating background color to demonstrate the blinking effect.
The makeBlink and stopBlink methods work in tandem with the new props timeBetweenSteps and blinkerId.
when the button is clicked, I want the blinky dancer to blink, this
should happen via the step function.
Buttons are wired up to trigger starting and stopping the blinking effect. I've replaced step in your example with simple and declarative methods for making the blink and stopping it.
makeBlinkButton.addEventListener("click", function () {
myBlinkyDancer.makeBlink();
});
The blinking effect uses setInterval to toggle the background color using the timeBetweenSteps prop as the interval duration. startBlink starts the interval and sets blinkerId to the corresponding interval ID. To stop the blinking effect, clearInterval is passed the blinkerId to clear it (stopping the blinking effect) before resetting it to null.
BlinkyDancer.prototype.makeBlink = function () {
if (this.blinkerId !== null) {
return;
}
let count = 0;
let blinkerId = setInterval(() => {
// do whatever thing you want to indicate blinking/dancing
if (count % 2 === 0) {
this.$node.css("background-color", "red");
} else {
this.$node.css("background-color", "blue");
}
count++;
}, this.timeBetweenSteps);
this.blinkerId = blinkerId;
console.log("blinkder id set: ", this.blinkerId);
};
BlinkyDancer.prototype.stopBlink = function () {
if (this.blinkerId === null) {
// already blinking
return;
}
if (this.blinkerId !== null) {
clearInterval(this.blinkerId);
console.log("blink id cleared", this.blinkerId);
this.blinkerId = null;
}
};
Maybe I'm calling [step] incorrectly in the dancer superclass?
It looks like you were on the right track but missing parens to call the method. In your example code, rather than:
makeBlinkyDancer.prototype.step;
You would do:
makeBlinkyDancer.prototype.step();
Related
I have 3 clickable objects. When one is clicked, this becomes the 'selected unit'.
I am trying to create some generic actions for the units such as moving to a point.
In my create function I initialize the units, when a unit is clicked on - this is supposed to become the 'selected unit' so that my movement and direction function applies to the this unit. However, the script is not able to recognize which unit intend for example I get this error:
Uncaught TypeError: Cannot read property 'velocity' of undefined.
Is there a way to use a variable to indicate selected users and pass that to the functions?
window.onload = function() {
var block_count = 0;
var block = '';
var selected_unit = '';
var unit_clicked = 0;
var tank1 = null;
var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create, update: update, render: render});
function preload () {
game.load.image('block', 'block.png');
game.load.image('tank1', 'tank.png');
game.load.image('baddie', 'tank.png');
game.load.image('mouse_btn', 'block.png');
game.input.mouse.capture = true;
}
function create () {
game.physics.startSystem(Phaser.Physics.ARCADE);
mouse_btn = game.add.sprite(30,30, 'mouse_btn');
mouse_btn.anchor.setTo(0.5, 0.5);
//T1
tank1 = game.add.sprite(30,30, 'tank1');
initialise_player(tank1);
game.physics.enable(tank1, Phaser.Physics.ARCADE);
//T2
tank2 = game.add.sprite(30,60, 'tank1');
initialise_player(tank2);
game.physics.enable(tank2, Phaser.Physics.ARCADE);
game.world.setBounds(0, 0, 2000, 2000);
game.camera.follow(tank1);
}
function update () {
if(selected_unit == '') {
mouse_btn.x = game.input.mousePointer.worldX
mouse_btn.y = game.input.mousePointer.worldY
}
if(game.input.activePointer.leftButton.isDown && block_count == 0 && unit_clicked == 0) {
game.input.activePointer.leftButton.stop(event);
block_count =1;
block = game.add.sprite(game.input.mousePointer.worldX, game.input.mousePointer.worldY, 'block');
game.physics.enable(block, Phaser.Physics.ARCADE);
block.anchor.setTo(0.5, 0.5)
lookAtObject(selected_unit, block, 0.005);
}
if(block.alive){
game.physics.arcade.moveToObject(selected_unit, block, 260, 0)
} else {
console.log(selected_unit)
selected_unit.body.velocity.x = 0;
selected_unit.body.velocity.y = 0;
}
if(game.physics.arcade.collide(selected_unit, block)) {
block_count--;
block.kill();
}
}
function render(){
//console.log(game.physics.arcade.collide(tank1, block))
}
function lookAtObject(obj, target, rotspeed){
var angle = Math.atan2(block.y - tank1.y, block.x - tank1.body.x);
tank1.rotation = angle + game.math.degToRad(90);
}
function initialise_player(tank1){
tank1.anchor.setTo(0.5, 0.5);
tank1.inputEnabled = true;
tank1.input.useHandCursor = true;
tank1.events.onInputDown.add(t1_clicked,this);
tank1.events.onInputOver.add(t1_over, this)
tank1.events.onInputOut.add(t1_out, this)
}
function t1_clicked() {
selected_unit = tank1;
}
function t1_over() {
unit_clicked = 1
}
function t1_out () {
unit_clicked = 0
}
};
The error you're getting on initial load is because in update you're making an assumption that selected_unit exists and has a body.
Update your third if to make sure selected_unit is defined.
if (selected_unit !== '') {
selected_unit.body.velocity.x = 0;
selected_unit.body.velocity.y = 0;
}
However, a better option would be to put this a few lines down, where you kill the block instead.
if(game.physics.arcade.collide(selected_unit, block)) {
selected_unit.body.velocity.x = 0;
selected_unit.body.velocity.y = 0;
block_count--;
block.kill();
}
if (block.alive); moveToObject is also expecting selected_unit to exist and have a body, which may not be the case; wrap it with a check.
if (selected_unit !== '') {
game.physics.arcade.moveToObject(selected_unit, block, 260, 0)
}
That now allows tank1 to rotate to look at the item you just placed, but it doesn't move it until it or tank2 have been clicked on.
This also points out that there are a number of tweaks you'll want to make to your code in general, since you're ignoring arguments that are being passed in. For example, t1_clicked isn't using the sprite that's been clicked on, but is instead just hard-coding tank1. lookAtObject isn't using obj or target, but again has values hard-coded in.
One other thing you may want to change is the following:
if(selected_unit == '') {
mouse_btn.x = game.input.mousePointer.worldX
mouse_btn.y = game.input.mousePointer.worldY
}
If you make that the following, you won't end up with an extra sprite hanging about on the screen.
if (block_count === 0) {
mouse_btn.x = game.input.mousePointer.worldX;
mouse_btn.y = game.input.mousePointer.worldY;
}
Is it possible to hook into a Canvas animation that has already been drawn?
I'm trying to create 3 seperate tracks that must be controlled by a "Start" and "Stop" button, but when pressing the Start button of the first canvas , it makes the last canvas run.
See current codepen progress
This is what I have so far.
class Animation{
constructor(){
this.options = {
left : 0,
animate : false
}
let tracks = $('.line');
let self = this;
tracks.each(function(){
self.startbtn = $(this).find('button.start');
self.stopbtn = $(this).find('button.stop');
self.canvas = $(this).find('canvas').get(0);
self.canvas.width = 1000;
self.canvas.height = 30;
self.ctx = self.canvas.getContext('2d');
self.draw(self.canvas,self.ctx);
self.startbtn.bind('click',() => {
self.options.animate = true;
self.draw(self.canvas,self.ctx);
})
});
}
draw(c,ctx){
ctx.clearRect(0,0,c.height,c.width);
ctx.fillRect(this.options.left,0,1,30);
if(this.options.animate){
requestAnimationFrame(() => {
console.log('done');
this.options.left += 1;
console.log(this.options.left)
this.draw(c,ctx);
});
}
}
}
new Animation();
Your code has scope issues and you had to insure that each track is operating separate from its constructor.
This is where i'd made sure information is entirely independent for the given track/parent element.
this.options = *set*;//left and animate separate for own process
this.node = *set*;//nodes for each track
this.ctx = *set*;//draw command context set at the parent
this.draw = *set*;//draw function moved to track
Here is complete edit to your code:
class Animation{
constructor(){
var tracks = $('.line');
var self = this;
tracks.each(function () {
this.options = {//options seperate to parent track node
left: 0,
animate: false
};
this.node = {//store child nodes for access
startbtn: $(this).find('button.start'),
stopbtn: $(this).find('button.stop'),
canvas: $(this).find('canvas').get(0)
};
this.node.canvas.width = 1000;
this.node.canvas.height = 30;
this.ctx = this.node.canvas.getContext('2d');
this.node.startbtn.bind('click',() => {// () => parentNode instantiation
this.options.animate = true;
this.draw(this.node.canvas, this.ctx);
});
this.draw = self.draw;
});
}
draw(c,ctx){
parent = c.parentNode;
ctx.clearRect(0,0,c.height,c.width);
ctx.fillRect(parent.options.left,0,1,30);
if(parent.options.animate){
requestAnimationFrame(() => {
console.log('done');
parent.options.left += 1;
console.log(parent.options.left)
parent.draw(c,ctx);
});
}
}
}
new Animation();
Scope and object handling needed to be done.
http://codepen.io/anon/pen/JKzBLK
My index.js looks like this
'use strict';
// Famous dependencies
var DOMElement = require('famous/dom-renderables/DOMElement');
var FamousEngine = require('famous/core/FamousEngine');
var CustomNode = require('./CustomNode');
var Animation = require('./components/Animation');
// Boilerplate code to make your life easier
FamousEngine.init();
var scene = FamousEngine.createScene('body');
var rootNode = scene.addChild();
var plane = rootNode.addChild(new CustomNode('url(images/plane_100x100.png)', '#B3E5FC', 200, 200, 100, 100));
var animation = new Animation(plane);
animation.start();
where CustomNode.js is
var DOMElement = require('famous/dom-renderables/DOMElement');
var FamousEngine = require('famous/core/FamousEngine');
var Transitionable = require('famous/transitions/Transitionable');
var Size = require('famous/components/Size');
var Node = require('famous/core/Node');
function CustomNode(bgUrl, bgColor, xpos, ypos, width, height) {
Node.call(this);
this.setSizeMode('absolute', 'absolute')
.setAbsoluteSize(width, height)
.setPosition(xpos,ypos);
this.nodeDomElement = new DOMElement(this);
this.nodeDomElement.setProperty('backgroundImage', bgUrl);
this.nodeDomElement.setProperty('zIndex', '2');
this.nodeDomElement.setProperty('background-color', bgColor);
}
CustomNode.prototype = Object.create(Node.prototype);
CustomNode.prototype.constructor = CustomNode;
module.exports = CustomNode;
And Animation.js looks like this
var FamousEngine = require('famous/core/FamousEngine');
var Transitionable = require('famous/transitions/Transitionable');
// A component that will animate a node's position in x.
function Animation (node) {
// store a reference to the node
this.node = node;
// get an id from the node so that we can update
this.id = node.addComponent(this);
// create a new transitionable to drive the animation
this.xPosition = new Transitionable(100);
}
Animation.prototype.start = function start () {
// request an update to start the animation
this.node.requestUpdate(this.id);
// begin driving the animation
this.xPosition.from(100).to(1000, {duration: 1000});
this.node.requestUpdate(this.id);
};
Animation.prototype.onUpdate = function onUpdate () {
// while the transitionable is still transitioning
// keep requesting updates
if (this.xPosition.isActive()) {
// set the position of the component's node
// every frame to the value of the transitionable
this.node.setPosition(this.xPosition.get());
this.node.requestUpdateOnNextTick(this.id);
}
};
module.exports = Animation;
But when I run famous dev, I get the following error
Uncaught TypeError: this._queue[1] is not a function
Please note that I am working off of the develop branch of famous/engine
This code is posted on github
https://github.com/codesdk/famous_engine_issue_debug_animation
for easy cloning
Please use the instructions in the README
Be sure to pass the correct arguments into Transitionable#to:
this.xPosition.from(100).to(1000, 'linear', 1000);
You may have mixed up the set and to methods of Transitionable.
this.xPosition.from(100).to(1000, {duration: 1000});
Should be:
this.xPosition.from(100).to(1000, 'linear', 1000);
Animation.prototype.start = function start () {
// request an update to start the animation
this.node.requestUpdate(this.id);
// begin driving the animation
this.xPosition.from(100).to(1000, 'linear', 1000);
this.node.requestUpdate(this.id);
};
Remember that setPosition of a node takes all three axis setPosition(x,y,z)
Animation.prototype.onUpdate = function onUpdate () {
// while the transitionable is still transitioning
// keep requesting updates
if (this.xPosition.isActive()) {
// set the position of the component's node
// every frame to the value of the transitionable
this.node.setPosition(this.xPosition.get(), 0, 0);
this.node.requestUpdateOnNextTick(this.id);
}
};
Well i have this SVG canvas element, i've got to the point so far that once a user clicks and drags the canvas is moved about and off-screen elements become on screen etc....
However i have this is issue in which when ever the user then goes and click and drags again then the translate co-ords reset to 0, which makes the canvas jump back to 0,0.
Here is the code that i've Got for those of you whio don't wanna use JS fiddle
Here is the JSfiddle demo - https://jsfiddle.net/2cu2jvbp/2/
edit: Got the solution - here is a JSfiddle DEMO https://jsfiddle.net/hsqnzh5w/
Any and all sugesstion will really help.
var states = '', stateOrigin;
var root = document.getElementById("svgCanvas");
var viewport = root.getElementById("viewport");
var storeCo =[];
function setAttributes(element, attribute)
{
for(var n in attribute) //rool through all attributes that have been created.
{
element.setAttributeNS(null, n, attribute[n]);
}
}
function setupEventHandlers() //self explanatory;
{
setAttributes(root, {
"onmousedown": "mouseDown(evt)", //do function
"onmouseup": "mouseUp(evt)",
"onmousemove": "mouseMove(evt)",
});
}
setupEventHandlers();
function setTranslate(element, x,y,scale) {
var m = "translate(" + x + "," + y+")"+ "scale"+"("+scale+")";
element.setAttribute("transform", m);
}
function getMousePoint(evt) { //this creates an SVG point object with the co-ords of where the mouse has been clicked.
var points = root.createSVGPoint();
points.x = evt.clientX;
points.Y = evt.clientY;
return points;
}
function mouseDown(evt)
{
var value;
if(evt.target == root || viewport)
{
states = "pan";
stateOrigin = getMousePoint(evt);
console.log(value);
}
}
function mouseMove(evt)
{
var pointsLive = getMousePoint(evt);
if(states == "pan")
{
setTranslate(viewport,pointsLive.x - stateOrigin.x, pointsLive.Y - stateOrigin.Y, 1.0); //is this re-intializing every turn?
storeCo[0] = pointsLive.x - stateOrigin.x
storeCo[1] = pointsLive.Y - stateOrigin.Y;
}
else if(states == "store")
{
setTranslate(viewport,storeCo[0],storeCo[1],1); // store the co-ords!!!
stateOrigin = pointsLive; //replaces the old stateOrigin with the new state
states = "stop";
}
}
function mouseUp(evt)
{
if(states == "pan")
{
states = "store";
if(states == "stop")
{
states ='';
}
}
}
In your mousedown function, you are not accounting for the fact that the element might already have a transform and you are just overwriting it.
You are going to need to either look for, and parse, any existing transform. Or an easier approach would be to keep a record of the old x and y offsets and when a new mousedown happens add them to the new offset.
have an object of a class Abon and then i want this object to move around the page.
a = new Abon();
a.init();
a.move();
the method move() contains:
function abon_move () {
var x = this.x;
var y = this.y;
var direction_x = Math.random()*5 - 5;
var direction_y = Math.random()*5 - 5;
var x_new = x + direction_x * this.movement_rate;
var y_new = y + direction_y * this.movement_rate;
console.log(x_new+" "+y_new)
$(".abonent."+this.id).animate( {
left:+x_new,
top:+y_new
}, 'slow', "linear", function() { this.move() });
}
All i want is that the method move (represented as function abon_move()) repeated again and again, after the animate stops. But the problem is that this.move() shown in callback has no connection to my object, because this in that place points to the HTML element, selected by jQuery.
UPD:
function Abon(id) {
...
this.move = abon_move;
...
}
Abon.prototype.move = abon_move;
And the actual method is the same, but with no callback in animate
then i try doing the following:
setInterval( a[0].move , 300); //doesn't work - says `this` members are undefined
setInterval( a[0].move() , 300); //works only one time and stops
Thank you for any help!
Try this :
function abon_move () {
var x = this.x;
var y = this.y;
var class = this;
...
}
Then, inside your jQuery animate, your can refer to your class using the variable class
Wrap the function abon_move() in a setTimeout call, as such: setTimeout(abon_move, 300); so it will run every 300 ms.