I want to have the separate buttons on my website, and each button moves the camera to a different position. How do I go about doing this. Currently, I have it set up so that when I press a button, the camera follows a chain of camera positions I have set, But I do not know how to separate these so each button moves the camera to a different spot in my scene.
Here is the code I currently have:
camera.position.set(100, 0, 400);
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
TWEEN.update();
}
function moveCam() {
var pos1 = new TWEEN.Tween(camera.position).to({
y: 300
}, 3000).easing(TWEEN.Easing.Quadratic.InOut);
var pos2 = new TWEEN.Tween(camera.position).to({
x: -400
}, 4000).easing(TWEEN.Easing.Quadratic.InOut);
var pos3 = new TWEEN.Tween(camera.position).to({
y: -10
}, 4000).easing(TWEEN.Easing.Quadratic.InOut);
var rot1 = new TWEEN.Tween(camera.rotation).to({
y: -1
}, 4000).easing(TWEEN.Easing.Quadratic.InOut);
var pos4 = new TWEEN.Tween(camera.position).to({
x: 600
}, 6000).easing(TWEEN.Easing.Quadratic.InOut);
var pos5 = new TWEEN.Tween(camera.position).to({
y: -400
}, 2000).easing(TWEEN.Easing.Quadratic.InOut);
var rot2 = new TWEEN.Tween(camera.rotation).to({
y: -5
}, 2000).easing(TWEEN.Easing.Quadratic.InOut);
var pos6 = new TWEEN.Tween(camera.position).to({
z: 10
}, 5000).easing(TWEEN.Easing.Quadratic.InOut);
var rot3 = new TWEEN.Tween(camera.rotation).to({
y: 0
}, 2000).easing(TWEEN.Easing.Quadratic.InOut);
pos1.start();
pos1.chain(pos2);
pos2.chain(pos3, rot1)
rot1.chain(pos4)
pos4.chain(pos5, rot2)
rot2.chain(pos6)
pos6.chain(rot3)
Maybe try the following:
Use a single tween instead of one per button. This way you can make sure they won't interfere:
var positionTween = new TWEEN.Tween(camera.position)
.easing(TWEEN.Easing.Quadratic.InOut);
var rotationTween = new TWEEN.Tween(camera.rotation)
.easing(TWEEN.Easing.Quadratic.InOut);
And define the positions and rotations for each of the buttons in their complete form. So, if you define a position, always define it with all three components. Same goes for the rotation. If you don't specify a value, it will not be changed so the position would then depend on where the camera was before.
Here, I'm using the buttons id-attribute to retrieve the position. So I'm assuming the HTML for the buttons looks something like this:
<button id="button1" class="camera-button">Position 1</button>
And the JS would be:
var buttonCameraSettings = {
button1: {
position: {x: 1, y: 0, z: 0},
rotation: {x: 0, y: Math.PI, z: 0}
},
button2: {
// ...
}
};
You can now register an event-handler for all buttons and lookup the settings for the button:
var button1 = document.getElementById('button1');
button1.addEventListener('click', function(ev) {
var buttonId = ev.target.id;
var cameraSettings = buttonCameraSettings[buttonId];
updateCameraTweens(cameraSettings);
});
Now for the last part, the function updateCameraTweens would look like this:
function updateCameraTweens(params) {
if (params.position) {
positionTween.stop();
positionTween.to(params.position, 1000).start();
}
if (params.rotation) {
rotationTween.stop();
rotationTween.to(params.rotation, 1000).start();
}
}
If you need different durations per animation, you could just add those numbers to the parameters as well.
Another note regarding the rotations. For some mathematical reason it is generally not the best idea to animate the Euler-angles stored in camera.rotation. Animations tend to look better if the quaternion of the object is animated instead.
var quatTween = new TWEEN.Tween(camera.quaternion);
var toQuaternion = new THREE.Quaternion();
var toEuler = new THREE.Eueler();
// in updateCameraTweens()
toEuler.set(rotation.x, rotation.y, rotation.z);
toQuaternion.setFromEuler(toEuler);
quatTween.to(toQuaternion, 1000).start();
For your scene is render auto, so just change the camere like this:
html:
<button onclick="move()">Move</button>
js:
function move()
{
camera.position += 10;
}
Related
So I have been using the Chebyshev Distance example from the Phaser labs, and while this example was using one layer, I happen to be using two, and when i set transparency on them, the colors start leaking into each other, especially on light colors.
Is there any way to circumvent or get rid of this effect
If the problem is that you have two layers, one ontop of the other and you are making both transparent (or only the top one), and you don't want that color to pass through, the solution could be to hide the tiles on the bottom layer.
Just check in the map-tile-loop, if the tile, where you want to change the alpha, has a tile beneath it, and if so make that background tile transparent.
Here a small working demo:
(The main magic is in the updateMap function)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
preload,
create
}
};
var player;
var bgLayer;
var point1 = {x: 250, y: 31};
var isLeaking = false;
new Phaser.Game(config);
function preload (){
this.load.image('tiles', 'https://labs.phaser.io/assets/tilemaps/tiles/catastrophi_tiles_16.png');
this.load.tilemapCSV('map', 'https://labs.phaser.io/assets/tilemaps/csv/catastrophi_level2.csv');
}
function create () {
this.add.text(50, 1, ' <- Background is visible, if no tiles are ontop')
.setOrigin(0)
.setDepth(100)
.setStyle({fontFamily: 'Arial'});
this.infoText = this.add.text(10, 20, 'Click to toggle leaking: on')
.setOrigin(0)
.setDepth(100)
.setStyle({fontFamily: 'Arial'});
// Just creating image for second layer tiles //
let graphics = this.make.graphics();
graphics.fillStyle(0xff0000);
graphics.fillRect(0, 0, 16, 16);
graphics.generateTexture('tiles2', 16, 16);
// Just creating image for second layer tiles //
let map = this.make.tilemap({ key: 'map', tileWidth: 16, tileHeight: 16 });
let tileset = map.addTilesetImage('tiles');
let tileset2 = map.addTilesetImage('tiles2');
bgLayer = map.createBlankLayer('background', tileset2);
bgLayer.fill(0);
let fgLayer = map.createLayer(0, tileset, 0, 0);
// Just to show that the Background is still show if not Tile is covering
fgLayer.removeTileAt(0, 0);
fgLayer.removeTileAt(1, 0);
fgLayer.removeTileAt(2, 0);
player = this.add.rectangle(point1.x, point1.y, 5, 5, 0xffffff, .5)
.setOrigin(.5);
this.input.on('pointerdown', () => {
isLeaking = !isLeaking;
this.infoText.setText( `Click to toggle leaking: ${isLeaking?'off':'on'}` )
updateMap(map);
});
updateMap(map);
}
function updateMap (map) {
let originPoint1 = map.getTileAtWorldXY(point1.x, point1.y);
console.info(map.layers.sort((a,b) => b.depth - a.depth))
map.forEachTile(function (tile) {
var dist = Phaser.Math.Distance.Chebyshev(
originPoint1.x,
originPoint1.y,
tile.x,
tile.y
);
let bgTile = bgLayer.getTileAt(tile.x, tile.y, false)
let hideOnlyTheseTiles = [ 0, 1, 2, 3, 4]; // Indexes to hide
if( !isLeaking ){
if(hideOnlyTheseTiles.indexOf(bgTile.index) > -1){ // here yopu can select the
bgTile.setAlpha(0);
}
} else{
bgTile.setAlpha(1);
}
tile.setAlpha(1 - 0.09 * dist);
});
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I'm currently using Phaser 3, although my question isn't technically restricted to that framework, as it's more of a general JS/canvas/maths question, but:
I have a line drawn with graphics(). It’s anchored at one end, and the other end is draggable. I made a quick demo and so far, so good - you can see what I have already on CodePen.
Dragging the marker around and redrawing the line is no problem, but what I’d like is for that line to have a maximum length of 100, so even if you’re still dragging beyond that point, the line would still follow the mouse, but not get any longer than 100. Dragging inside that maximum radius, the line would shrink as normal.
I’ve put together a visual that hopefully explains it:
The issue is that I suspect this is VERY MATHS and I am very, very weak with maths. Could anyone explain like I’m five what I need to do to my code to achieve this?
Edit: Adding code in a snippet here, as requested:
var config = {
type: Phaser.AUTO,
width: 800,
height: 400,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: {
preload: preload,
create: create,
update: update
}
};
var path;
var curve;
var graphics;
var game = new Phaser.Game(config);
function preload() {
this.load.spritesheet('dragcircle', 'https://labs.phaser.io/assets/sprites/dragcircle.png', { frameWidth: 16 });
}
function create() {
graphics = this.add.graphics();
path = { t: 0, vec: new Phaser.Math.Vector2() };
curve = new Phaser.Curves.Line([ 400, 390, 300, 230 ]);
var point0 = this.add.image(curve.p0.x, curve.p0.y, 'dragcircle', 0);
var point1 = this.add.image(curve.p1.x, curve.p1.y, 'dragcircle', 0).setInteractive();
point1.setData('vector', curve.p1);
this.input.setDraggable(point1);
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
gameObject.x = dragX;
gameObject.y = dragY;
gameObject.data.get('vector').set(dragX, dragY);
});
this.input.on('dragend', function (pointer, gameObject) {
let distance = Phaser.Math.Distance.Between(curve.p0.x, curve.p0.y, curve.p1.x, curve.p1.y);
console.log(distance);
});
}
function update() {
graphics.clear();
graphics.lineStyle(2, 0xffffff, 1);
curve.draw(graphics);
curve.getPoint(path.t, path.vec);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/3.55.2/phaser.min.js"></script>
You are right, you would need some math, but phaser has many helper functions, that will do the heavy lifting.
The main idea is, of this solution is
define a maxLength
get the the new point on drag, and create a real Phaser Vector2
here is some math is needed, to create the vector, just calculate destination point minus origin point
new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y) (origin point being the starting point of the desired vector, and destination point being the mouse pointer)
calculate the length of the created vector and compare it with the maxLength
if too long adjust the vector, with the handy function setLength (link to the documentation, this is where you would have needed math, but thankfully Phaser does it for us)
set the new coordinates for point1 and the curve endpoint
Here a quick demo (based on your code):
var config = {
type: Phaser.AUTO,
width: 500,
height: 170,
scene: {
preload: preload,
create: create,
update: update
}
};
var curve;
var graphics;
var game = new Phaser.Game(config);
function preload() {
this.load.spritesheet('dragcircle', 'https://labs.phaser.io/assets/sprites/dragcircle.png', { frameWidth: 16 });
}
function create() {
graphics = this.add.graphics();
curve = new Phaser.Curves.Line([ config.width/2, config.height - 20, config.width/2, 10 ]);
// define a length, could be a global constant
let maxLength = curve.p0.y - curve.p1.y;
var point0 = this.add.image(curve.p0.x, curve.p0.y, 'dragcircle', 0);
var point1 = this.add.image(curve.p1.x, curve.p1.y, 'dragcircle', 0).setInteractive();
this.input.setDraggable(point1);
// Just add for Debug Info
this.add.circle(curve.p0.x, curve.p0.y, maxLength)
.setStrokeStyle(1, 0xffffff, .5)
this.input.on('drag', function (pointer) {
let vector = new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y);
let distance = Phaser.Math.Distance.Between( point0.x, point0.y, pointer.x, pointer.y);
if(distance > maxLength){
vector.setLength(maxLength);
}
point1.x = point0.x + vector.x;
point1.y = point0.y + vector.y;
curve.p1.x = point1.x;
curve.p1.y = point1.y;
});
// NOT REALLY NEEDED
/*this.input.on('dragend', function (pointer, gameObject) {
let distance = Phaser.Math.Distance.Between(curve.p0.x, curve.p0.y, curve.p1.x, curve.p1.y);
console.log(distance);
});*/
}
function update() {
graphics.clear();
graphics.lineStyle(2, 0xffffff, 1);
curve.draw(graphics);
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Optional - Code Version using Phaser.GameObjects.Line:
This uses less code, and thanks to the Line GameObject (link to Documentation), you can directly use the vector to update the line, and also don't need the update function, graphics and so.
const config = {
type: Phaser.CANVAS,
width: 500,
height: 160,
scene: {
create
}
};
const game = new Phaser.Game(config);
const MAX_LINE_LENGTH = 100;
function create() {
let points = [ {x: config.width/2, y: config.height - 20}, {x: config.width/2, y: config.height - 120} ];
let point0 = this.add.circle(points[0].x, points[0].y, 6)
.setStrokeStyle(4, 0xff0000);
let point1 = this.add.circle(points[1].x, points[1].y, 6)
.setStrokeStyle(4, 0xff0000)
.setInteractive();
this.input.setDraggable(point1);
// Just add for Debug Info
this.add.circle(point0.x, point0.y, MAX_LINE_LENGTH)
.setStrokeStyle(1, 0xffffff, .5);
let line = this.add.line(points[0].x, points[0].y, 0, 0, 0, -100, 0x00ff00)
.setOrigin(0);
this.input.on('drag', function (pointer) {
let vector = new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y);
let distance = Phaser.Math.Distance.Between( point0.x, point0.y, pointer.x, pointer.y);
if(distance > MAX_LINE_LENGTH){
vector.setLength(MAX_LINE_LENGTH);
}
point1.x = point0.x + vector.x;
point1.y = point0.y + vector.y;
line.setTo(0, 0, vector.x, vector.y);
});
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I want to make it so the tween animation for my camera only starts when an object is clicked. This object could be an object in my three.js scene, or simply a HTML button. Here is my code for the camera animation, which works:
// initial position of camera
var camposition = { x : 0, y: 0, z:3100 };
// target position that camera tweens to
var camtarget = { x : 0, y: 0, z:8500 };
var tweencam = new TWEEN.Tween(camposition).to(camtarget, 1600);
tweencam.onUpdate(function(){
camera.position.x = camposition.x;
camera.position.y = camposition.y;
camera.position.z = camposition.z;
});
// delay tween animation by three seconds
tweencam.delay(3000);
tweencam.easing(TWEEN.Easing.Exponential.InOut);
tweencam.start();
Instead of delaying the animation by three seconds, I want to detect when the mouse1 button has been clicked, and then start the animation. Not sure how to do this, or if there is an easier alternative method?
All you have to do is wrap the start of the tween inside a function, and then call this function on a button click:
function launchTween() {
tweencam.start();
}
<button onclick="launchTween()">Launch Tween</button>
The entire code would look as follows:
// initial position of camera
var camposition = {
x: 0,
y: 0,
z: 3100
};
// target position that camera tweens to
var camtarget = {
x: 0,
y: 0,
z: 8500
};
var tweencam = new TWEEN.Tween(camposition).to(camtarget, 1600);
tweencam.onUpdate(function() {
camera.position.x = camposition.x;
camera.position.y = camposition.y;
camera.position.z = camposition.z;
});
tweencam.easing(TWEEN.Easing.Exponential.InOut);
function launchTween() {
tweencam.start();
}
<button onclick="launchTween()">Launch Tween</button>
Hope this helps! :)
I would like to make a "prototype" of animations for a future game. But I'm totally a noob in kineticJS.
I have an object where I make all my functions:
var app = {}
I have a function init to build a layer, a stage and declare that I will use requestAnimationFrame:
init: function(){
layer = new Kinetic.Layer();
DrawingTab = [];
stage = new Kinetic.Stage({
container: 'canvasDemo',
width: 800,
height: 600
});
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
}
Secondly, I've got one function to build my rects:
createObject: function(){
rect = new Kinetic.Rect({
x: 50,
y: 50,
width: 150,
height: 150,
fill: 'black',
name: 'batteur',
id: 'batteur'
});
rect1 = new Kinetic.Rect({
x: 300,
y: 50,
width: 150,
height: 150,
fill: 'black',
name: 'batteur1',
id: 'batteur1'
});
rect2 = new Kinetic.Rect({
x: 550,
y: 50,
width: 150,
height: 150,
fill: 'black',
name: 'batteur2',
id: 'batteur2'
});
layer.add(rect);
layer.add(rect1);
layer.add(rect2);
stage.add(layer);
DrawingTab.push(rect,rect1,rect2,rect3,rect4,rect5);
}
That's all I did. And then, I want to know how to animate like that:
every 20 secondes, one of the rect (select randomly) change of color,
and the user have to click on it.
the user have 5sec to click on it, and if he doesn't click, the rect change to the beginning color.
I hope explanations are clear and something will can help me, because I'm totally lost.
You should use Kinetic.Animation for animations because it optimizes redraws. Here's an example
If your game is using sprites, you should be using the Sprite shape. Here's an example of that
You don't need requestAnimationFrame or Kinetic.Animation to handle this, considering the kind of animation you want. Only use animations if you need to change the animation status every frame.
See this working DEMO.
Using setInterval and setTimeout the application became more performant.
I reduce the time of change of color to 5 seconds and the time to click to 2 seconds, just to quickly visualization of the features.
Here is the code added:
// times (make changes according)
var timeToChange = 5000; // 5 seconds
var timeToClick = 2000; // 2 seconds
// render all rects
layer.drawScene();
// add a logical rect for each rect in DrawingTab
var LogicalTab = [];
for (var i = 0; i < DrawingTab.length; ++i) {
LogicalTab.push({
isPressed: false,
frame: 0
});
}
// return a random integer between (min, max)
function random(min, max) {
return Math.round(Math.random() * (max - min) + min);
};
// define colors
var colors = ["red", "green", "blue"];
// reset state of current rect
function reset(n) {
var drect = DrawingTab[n];
var lrect = LogicalTab[n];
// check if current rect was clicked
setTimeout(function () {
if (!lrect.isPressed) {
drect.setFill("black");
// redraw scene
layer.drawScene();
lrect.frame = 0;
}
// turn off click event
drect.off("click");
}, timeToClick);
}
// start the animation
var start = setInterval(function () {
// select a rect randomly
var rand = random(0, 2);
var drect = DrawingTab[rand];
var lrect = LogicalTab[rand];
// change color
drect.setFill(colors[lrect.frame]);
// redraw scene
layer.drawScene();
// flag that current rect is not clicked
lrect.isPressed = false;
// check for click events
drect.on("click", function () {
// flag that current rect is clicked
lrect.isPressed = true;
// hold current color
lrect.frame++;
lrect.frame = lrect.frame % colors.length;
});
// reset current rect (only if it is not clicked)
reset(rand);
}, timeToChange);
I'm a newbye here, but I hope I'm able to help. KineticJS don't need requestAnimationFrame, because it has already something that handles animations. so first of all I think you should have a look to this page
if you want to make the rect's color change every 20 s, you may do something like this:
var anim = new Kinetic.Animation(function(frame) {
if(frame.time > 20000)
{
frame.time = 0;
colors = ['red', 'blue', 'violet'];
ora = colors[Math.floor(Math.random()*3)];
DrawingTab[Math.floor(Math.random*6)].setAttrs({fill: ora});
}
},layer);
then, for the 5sec stuff, I tried to write something
var currentRect = { value:0, hasClicked : true };
var anim2 = new Kinetic.Animation(function(frame) {
if(frame.time > 20000)
{
frame.time = 0;
colors = ['red', 'lightblue', 'violet'];
ora = colors[Math.floor(Math.random()*3)];
currentRect.hasClicked = false;
currentRect.value=Math.floor(Math.random()*6);
DrawingTab[currentRect.value].setAttrs({fill: ora});
}
if (!currentRect.hasClicked && frame.time>5000)
{
DrawingTab[currentRect.value].setAttrs({fill: 'black'});
currentRect.hasClicked = true;
}
DrawingTab[currentRect.value].on('click',function(){ if (frame.time<=5000) currentRect.hasClicked = true;});
},layer);
anim2.start();
I've just tried something similiar and it looks like it's working :)
p.s. sorry about my english, I'm only a poor italian student
p.p.s. I'm sure the code can be optimized, but for now I think it can be alright
I have managed to dynamically create an array of shapes, and they are nicely placed at different coordinates.
However, when I try to assign an event within that loop, the result of click is always the same. As if the click event is still referencing the last iteration of my loop.
What am I doing wrong? Thanks!
EDIT: Actually, re-produced this behaviour in an isolated environment:
var stage = new Kinetic.Stage({
container: 'container',
width: 1024,
height: 768
});
var layer = new Kinetic.Layer();
singleSegment=40;
for (var i = 0; i < 4; i++) {
depth=singleSegment+(singleSegment*i);
dotLabel = new Kinetic.Text({
x: depth,
y: depth,
text: "test"
});
dotLabel.on('click', function(evt){
console.log(this.x);
});
layer.add(dotLabel);
}
stage.add(layer);
How do I add different events to these four labels?
You are doing everything correct, I think. but because of this;
console.log(i);
The last value of i is array.length-1, and when it is clicked, it shows that value, which does not change because it's outside of loop when it is clicked.
This will show different value.
console.log(this.attrs.x);
I just had to deal with this same issue. I solved it by storing to each shape its location.
for (var axisItem=0;axisItem<innerCircleXAxisArray.length;axisItem++)
{
var arc = new Kinetic.Shape({
drawFunc: function(canvas){
var allAttrs = this.getAttrs();
var start = allAttrs['start'];
var end = allAttrs['end'];
var context = canvas.getContext();
context.strokeStyle = 'red';
var centerOfCanvasX = canvasWidth / 2;
var centerOfCanvasY = canvasHeight / 2;
context.translate(centerOfCanvasX, centerOfCanvasY);
context.lineWidth = 15;
context.beginPath();
context.arc(0, 0, 284, start , end, true);
canvas.stroke(this); // Fill the path
context.closePath();
context.translate(-centerOfCanvasX, -centerOfCanvasY);
},
fill: 'red',
stroke: 'red',
strokeWidth: 15
});
arc.setAttrs({'start': innerCircleXAxisArray[axisItem]['start'], 'end': innerCircleXAxisArray[axisItem]['end']});
layer.add(arc);
}
stage.add(layer);
When the object is created, I use setAttrs to store the object's location - a start and end angle since these are arcs, but it could just as easily be an x and y point. Then in the drawFunc I use getAttrs to retrieve that data and to draw the object.