How to resize a sprite pixi.js - javascript

I have some PixiJS code that generates a sprite:
let type = "WebGL";
if (!PIXI.utils.isWebGLSupported()) {
type = "canvas"
}
PIXI.utils.sayHello(type);
const app = new PIXI.Application({
width: 1000, height: 600, backgroundColor: 0x1099bb, resolution: window.devicePixelRatio || 1,
});
document.body.appendChild(app.view);
function renderSprite() {
const sprite = new PIXI.Sprite(PIXI.Texture.from("BEE-e1414622656271.png",));
sprite.anchor.set(0.5);
app.stage.addChild(sprite);
sprite.x = app.screen.width/2;
sprite.y = app.screen.height/2;
}
renderSprite();
The code works. However, the sprite generated is so large that it goes beyond the borders of the container.
I need to set the sprite's size, so I tried using the height and width options of baseTexture:
const sprite = new PIXI.Sprite(PIXI.Texture("BEE-e1414622656271.png", baseTexture=PIXI.baseTexture(options={
"height": 100,
"width": 100
})));
But I got an error stating that PIXI.baseTexture was not a function.
How do I resize the sprite so it fits within the container?

You can control position and size of sprite after it is created like this:
var sprites = {};
sprites.cat = new PIXI.Sprite(catTexture);
//Change the sprite's position
sprites.cat.x = 96;
sprites.cat.y = 96;
//Change the sprite's size
sprites.cat.width = 30;
sprites.cat.height = 150;
//Add the cat to the stage so you can see it
stage.addChild(sprites.cat);
and then later - even when this sprite is added to stage container - you can further modify its width and height.
Try putting in your game loop something like:
sprites.cat.width += 0.5;
See more in docs: https://github.com/kittykatattack/learningPixi#size-and-scale

Related

TypeError: Cannot Read Property of Undefined Even Though I Defined It

I'm a beginner to JavaScript. I've looked through a few questions on here regarding "TypeError: ... undefined even though the property is defined" but their examples are either too dense for me to grasp or they realize they've incorrectly assigned something downstream. Here I think my example is quite simple, and I don't think I've incorrectly assigned anything.
I've defined position in my class MoveableObjects and when I call player = new MoveableObject(position, other params ...) I don't get any errors.
When I change this to player = new Sprite(position, other params ...) where Sprite extends MoveableObjects and then try to call a function inside Sprite it tells me position.x is undefined. I put console.log(player) immediately after the declaration and it does in fact show position as undefined, but I don't understand why it does since I've clearly defined it.
Here is the constructor of MoveableObjects
class MoveableObject {
constructor({ colorRGB, width, height, position, velocity}) {
this.width = width;
this.height = height;
this.position = position;
this.velocity = velocity;
this.colorRGB = colorRGB;
// If the side of the object is touching or beyond the side of the canvas it is flagged as bounded.
this.inUpLimit = false;
this.inDownLimit = false;
this.inLeftLimit = false;
this.inRightLimit = false;
// Translate the velocity into directional words
this.movingLeft = false;
this.movingRight = false;
this.movingUp = false;
this.movingDown = false;
}
Here is the constructor of Sprite and the draw() function it uses.
// Sprite represents moveable objects with an animated image.
class Sprite extends MoveableObject {
constructor({ imageSrc, numRows = 1, numCols = 1, spacing = 0, margin = 0, animationSpeed = 10, position, velocity}) {
super(position, velocity)
this.image = new Image() // Image can be a single image or a spritesheet.
this.image.src = imageSrc
this.numRows = numRows // # animation sequences in the spritesheet
this.numCols = numCols // # frames in an animation sequence
this.spacing = spacing // # pixels between each frame
this.margin = margin // # pixels between image border and sprite border. Vertical and horizontal margins should be equal.
this.animation = new Object()
this.animation.speed = animationSpeed // # times draw function is called before the next frame in the animation sequence is called.
this.animation.counter = 0 // # times the draw function has been called
this.animation.enabled = false // Whether or not the animation sequence is currently running
this.animation.row = 1; // The row of the current frame being drawn
this.animation.col = 1; // The column of the current frame being drawn
this.animation.firstRow = 1; // The row of the frame to initialize the animation loop on
this.animation.firstCol = 1; // The column of the frame to initialize the animation loop on
this.animation.lastRow = 1; // The row of the frame to restart the animation loop on
this.animation.lastCol = 1; // The column of the frame to restart the animation loop on
this.frame = new Object();
this.frame.width = 0; // Init image.onload
this.frame.height = 0; // Init image.onload
this.image.onload = () => { // Calculates width and height of animation frame based on numRows, numColumns, spacing, and margin
let imageWidth = this.image.width;
let spriteSheetWidth = imageWidth - (2 * this.margin);
let widthNoSpacing = spriteSheetWidth - this.spacing * (this.numCols - 1);
let frameWidth = widthNoSpacing / this.numCols;
let imageHeight = this.image.height;
let spriteSheetHeight = imageHeight - (2 * this.margin);
let heightNoSpacing = spriteSheetHeight - this.spacing * (this.numRows - 1);
let frameHeight = heightNoSpacing / numRows;
this.frame.width = frameWidth;
this.frame.height = frameHeight;
}
}
draw() {
// Draw the frame at the current row and column.
context.drawImage(
this.image, // the entire image being loaded
this.animation.col * this.frame.width, // sx, x coordinate to begin crop
this.animation.row * this.frame.height, // sy, y coordinate to begin crop
this.frame.width, // swidth, width of cropped image
this.frame.height, // sheight, height of cropped image
this.position.x, // x coordinate where to place image on canvas
this.position.y, // y coordinate where to place image on canvas
this.frame.width, // the width to stretch/shrink it to
this.frame.height // the height to stretch/shrink it to
)
Here I create a new MoveableObject with position defined and then console.log() it. It shows position to be what I set it to. I copy/paste that declaration and change "new MoveableObject" to "new Sprite" (with a new variable name of course) and print that, and it shows position is undefined:
// Initializers for main()
const gravity = 0.8;
const player = new Sprite({
imageSrc: './lib/img/template.png',
numRows: 21,
numCols: 4,
spacing: 0,
margin: 0,
position: {
x: 0,
y: 0
},
velocity: {
x: 0,
y: 0
}
})
console.log(player)
const enemy = new MoveableObject({
colorRGB: 'blue',
width: 50,
height: 150,
position: {
x: 150,
y: 50
},
velocity: {
x: 0,
y: 0
}
})
console.log(enemy)
const moveableObjects = [player, enemy];
// The main function of the script updates the game state every frame.
function main() {
// When the game starts the canvas, then player, then enemy are rendered.
context.fillStyle = 'black';
context.fillRect(0, 0, canvas.width, canvas.height);
player.draw();
enemy.draw();
The error is raised on player.draw() and reads "Uncaught TypeError: Cannot read properties of undefined (reading 'x')"
Does anyone understand why this is the case? It seems to me like the error is maybe produced by not properly extending my class or using super() incorrectly to pass attributes, but I haven't found an example that proves that.

Use an existing canvas without losing contents in pIxi.js

I am quite new to Pixi.js so I'm sorry this is a stupid question.
I understand that if I would like to render pixi.js to an existing canvas I have to specify view.
const app = new PIXI.Application({
view: myExistingCanvas,
});
However, I realized that if I write like this, Pixi application actually overwrites my existing canvas and I end up losing all the contents inside "myExistingCanvas".
Could somebody advise me how I can create a pixi application on top of an existing canvas without overwriting?
Using the view property you can pass to the constrctor of a new PIXI.Application, we can tell it to use an existing Canvas. This canvas though doesn't necessarily have to be added to the DOM - it's enough if it exists 'virtually'.
So ultimately we need three Canvas instances - which all should have equal dimensions.
The first canvas would be the existing canvas you've mentioned in your question and act as an off-screen canvas
The second canvas is another empty off-screen canvas, which captures Pixi's output
The third canvas is actually the on-screen canvas which combines the output of the previous two canvases
Now you might wonder how to do this.
To do this we must intercept Pixi's update loop, which we can do by adding a ticker to PIXI.Ticker.shared.
Inside this update loop we need to do the following things:
Update Pixi's animations and call it's renderer
Clear the third (on-screen) canvas
Draw the contents of the first canvas to the third
Draw the contents of the second canvas to the third
Basically that's it - though it might sound a bit abstract.
Here's an example (Just click on 'Run code snippet'):
let offScreenCanvasA = document.createElement("canvas");
let offScreenCanvasB = document.createElement("canvas");
let onScreenCanvas = document.createElement("canvas");
let width = 400;
let height = 300;
offScreenCanvasA.width = width;
offScreenCanvasB.width = width;
onScreenCanvas.width = width;
offScreenCanvasA.height = height;
offScreenCanvasB.height = height;
onScreenCanvas.height = height;
document.body.appendChild(onScreenCanvas);
const app = new PIXI.Application({
view: offScreenCanvasB,
transparent: true,
width: 400,
height: 300
});
const container = new PIXI.Container();
const renderer = PIXI.autoDetectRenderer();
app.stage.addChild(container);
const texture = PIXI.Texture.from('https://picsum.photos/id/237/26/37');
for (let i = 0; i < 25; i++) {
const bunny = new PIXI.Sprite(texture);
bunny.anchor.set(0.5);
bunny.x = (i % 5) * 40;
bunny.y = Math.floor(i / 5) * 40;
container.addChild(bunny);
}
container.x = app.screen.width / 2;
container.y = app.screen.height / 2;
container.pivot.x = container.width / 2;
container.pivot.y = container.height / 2;
let ticker = PIXI.Ticker.shared
ticker.add(function(delta) {
container.rotation -= 0.01;
renderer.render(container);
onScreenCanvas.getContext("2d").clearRect(0, 0, width, height);
onScreenCanvas.getContext("2d").drawImage(offScreenCanvasA, 0, 0, width, height);
onScreenCanvas.getContext("2d").drawImage(offScreenCanvasB, 0, 0, width, height);
});
let image = new Image();
image.onload = function(e) {
offScreenCanvasA.getContext("2d").drawImage(e.target, 0, 0, width, height);
}
image.src = "https://picsum.photos/id/237/400/300";
<script src="https://d157l7jdn8e5sf.cloudfront.net/dev/pixi-legacy.js"></script>
The dog picture in the background is the from the first canvas, and the rotating grid of dogs Pixi's output to the second canvas.

Trouble animating spritesheet in EaselJS

I'm trying to animate a spritesheet using EaselJS, but I keep getting an uncaught type error: undefined is not a function on this line - bmpAnimation = new createjs.BitmapAnimation(spriteSheet);
Here is my code so far:
// JavaScript Document
window.onload = function(){
//Creating a new Stage instance, passing in our canvas element's ID.
var stage = new createjs.Stage("canvas"),
imgMonsterARun = new Image();
imgMonsterARun.src = "img/MonsterARun.png";
var spriteSheet = new createjs.SpriteSheet({
// image to use
images: [imgMonsterARun],
// width, height & registration point of each sprite
frames: {width: 64, height: 64, regX: 32, regY: 32},
animations: {
walk: [0, 9, "walk"]
}
});
// create a BitmapAnimation instance to display and play back the sprite sheet:
bmpAnimation = new createjs.BitmapAnimation(spriteSheet);
// start playing the first sequence:
bmpAnimation.gotoAndPlay("walk"); //animate
// set up a shadow. Note that shadows are ridiculously expensive. You could display hundreds
// of animated rats if you disabled the shadow.
bmpAnimation.shadow = new createjs.Shadow("#454", 0, 5, 4);
bmpAnimation.name = "monster1";
bmpAnimation.direction = 90;
bmpAnimation.vX = 4;
bmpAnimation.x = 16;
bmpAnimation.y = 32;
// have each monster start at a specific frame
bmpAnimation.currentFrame = 0;
stage.addChild(bmpAnimation);
createjs.Ticker.setFPS(60);
createjs.Ticker.useRAF = true;
createjs.Ticker.addListener(window);
function tick()
{
// Hit testing the screen width, otherwise our sprite would disappear
if (bmpAnimation.x >= screen_width - 16) {
// We've reached the right side of our screen
// We need to walk left now to go back to our initial position
bmpAnimation.direction = -90;
}
if (bmpAnimation.x < 16) {
// We've reached the left side of our screen
// We need to walk right now
bmpAnimation.direction = 90;
}
// Moving the sprite based on the direction & the speed
if (bmpAnimation.direction == 90) {
bmpAnimation.x += bmpAnimation.vX;
}
else {
bmpAnimation.x -= bmpAnimation.vX;
}
// update the stage:
stage.update();
}
tick();
};
Any help would be appreciated.
In 0.8.0 you can use the normal SpriteSheet to create an animated SpriteSheet. Checkout the Demo on http://createjs.com/Demos/EaselJS/SpriteSheet (make sure to check the code under "live-edit" ;-))
Try using "Sprite" instead of "BitmapAnimation".
That is
bmpAnimation = new createjs.BitmapAnimation(spriteSheet);
becomes
bmpAnimation = new createjs.Sprite(spriteSheet);
Worked for me.

Famo.us ScrollView scroll like in a chat

I have ScrollView and sequence of Surface elements in it.
I want to be able to scroll to the latest element as soon as it appears in the array.
Here is a demo, I want it to be scrolled just like in any chat application... old surfaces go beyond the scene, and new will be always at the bottom. The question is the same as here: Famo.us how to make a scrollview be filled from bottom to top (or right to left)?
http://jsfiddle.net/LA49a/
Famous.loaded(function () {
var Engine = Famous.Core.Engine;
var Surface = Famous.Core.Surface;
var Scrollview = Famous.Views.Scrollview;
var Timer = Famous.Utilities.Timer;
var mainContext = Engine.createContext();
var scrollview = new Scrollview();
var surfaces = [];
var i = 0;
scrollview.sequenceFrom(surfaces);
Timer.setInterval(function () {
var temp = new Surface({
content: "Surface: " + (i + 1),
size: [undefined, 50],
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: "50px",
textAlign: "center"
}
});
temp.pipe(scrollview);
surfaces.push(temp);
i++;
// scrollview.goToNextPage();
}, 400);
mainContext.add(scrollview);
});
This is supported in the famous-flex ScrollView by setting the alignment property to 1.
See this demo:
https://github.com/IjzerenHein/famous-flex-chat
I'm assuming you're putting in a very large, round number to get the scrollview to move to the end, which shows your 'latest' element.
If you've got surfaces that are the same width (assuming an X orientation of the scrollview) you might get a smoother result by checking for the actual number of pixels you need to move to position the last item at the right edge of scrollview. You have the original array that you ran sequenceFrom() on. Multiply the number of items in that by your surface width and subtract pixels for the scrollview's width (which could be set to 'undefined' in which case you'll need to look at the window's width) to adjust for getting the latest element to the right side of the scrollview instead of the left side.
If your surfaces are not the same size, you might change things to track the x-offset of each surface as you add them to the backing array by just adding a property to the surface. Then you could just ask the surface what its offset is and subtract the right amount for the width of the scrollview.
Hope that helps you get closer.
Solved, by rotating ScrollView by 180˚ and all surfaces inside it also by 180˚ http://jsfiddle.net/tamtamchik/LA49a/3/
Famous.loaded(function () {
var Engine = Famous.Core.Engine;
var Surface = Famous.Core.Surface;
var Scrollview = Famous.Views.Scrollview;
var Timer = Famous.Utilities.Timer;
var Transform = Famous.Core.Transform;
var StateModifier = Famous.Modifiers.StateModifier;
var ContainerSurface = Famous.Surfaces.ContainerSurface;
var mainContext = Engine.createContext();
var scrollview = new Scrollview();
var surfaces = [];
var i = 0;
var surfaceHeight = 50;
scrollview.sequenceFrom(surfaces);
Timer.setInterval(function () {
var container = new ContainerSurface({
size: [undefined, surfaceHeight]
});
var temp = new Surface({
content: "Surface: " + (i + 1),
size: [undefined, surfaceHeight],
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: "50px",
textAlign: "center"
}
});
var surfaceRotate = new StateModifier({
transform: Transform.rotateZ(Math.PI)
});
var surfacePush = new StateModifier({
transform: Transform.translate(window.innerWidth, surfaceHeight)
});
container.add(surfacePush).add(surfaceRotate).add(temp);
container.pipe(scrollview);
temp.pipe(scrollview);
surfaces.unshift(container);
i++;
}, 400);
var rotate = new StateModifier({
transform: Transform.rotateZ(Math.PI)
});
var push = new StateModifier({
transform: Transform.translate(window.innerWidth, window.innerHeight)
});
mainContext.add(push).add(rotate).add(scrollview);
});
hmm, that's pretty crafty! Its a couple of extra matrices which could get very slow if you have a lot of text content in those nodes tho...
By using a huge number, this is just to force famo.us to scroll to the end of the view, I assume?
if the render had happened you could also:
pos = getPosition(nodeNum) // for last elements scrollposition
setPosition(pos)
But if the insert happens in same code block as your movement control it's not rendered yet.
There's a complicated workaround offered here using events to trigger the sizing calculation after the deploy/render has occurred:
how can we get the size of a surface within famo.us?
and another trick here to make famous recalculate sizes from the inimitable #johntraver
how to make famo.us recalculate sizes?
did you also look at Engine.nextTick() or JQuery.deferred ?
It seems that immediately after you insert the content there will be a "not rendered" instant but after an update you could get the sizes OK.

Scale KineticJS Stage with Browser Resize?

Recently discovered KineticJS as I've been trying to convert my Flash skills to HTML Canvas. It is an extremely impressive tool!
Question:
If I'm using a front-end like Bootstrap and I have a page with several divs that contain KineticJS-generated canvases, can I get those canvases to scale along with everything else on the page?
Thanks much.
----- Due to character limit of comments, I'm replying in the OP. ---------
Would you set it up so that there is a maximum size and then have it scale as when the browser is scaled?
For example, in Actionscript I did this to track the size of the browser and scale the stage:
stageListener.onResize = function():Void {
// Calculate % of total allowable change for each dimension
var percentWScale:Number = (Stage.width - minStageWSize)/stageWResizeRange;
var percentHScale:Number = (Stage.height - minStageHSize)/stageHResizeRange;
// Test to see if the stage size is in the rescale range
if ((Stage.width < maxStageWSize) || (Stage.height < maxStageHSize)) {
pfarm.stageChanged = true;
// Calculate the scale factor for each dimension, but don't go below the minimum
var percentHScale:Number = Math.max(minimumScale, Stage.width/1024);
var percentVScale:Number = Math.max(minimumScale, Stage.height/768);
//trace ("H Scale Factor: "+percentHScale+". V Scale factor: "+percentVScale);
// Determine which window dimension to use for scaling -
// Use the one which is the most scaled down from the maximum stage size.
var getScaleFrom:String = (percentHScale << percentVScale)? "hScale" : "vScale";
// Scale the object
if (getScaleFrom == "hScale") {
baseParent._width = projectContentXSize * percentHScale;
baseParent._height = projectContentYSize * percentHScale;
} else {
baseParent._width = projectContentXSize * percentVScale;
baseParent._height = projectContentYSize * percentVScale;
}
} // END RESIZE OF SCROLLPANE CONTENT
For the benefit of flash refugees (like me) who are crossing the complex border into HTML5 land, could you provide an example or 2?
Sure!
But, be careful about scaling the Kinetic container element with CSS (as Bootstrap does) because the browser will resize by "stretching" the pixels rather than letting Kinetic redraw the pixels.
Since html canvas (and therefore Kinetic objects) are really just pixels, the common warnings about resizing also apply when scaling the Kinetic container.
The drawings will distort if you scale horizontally and vertically by different factors.
Drawings that are scaled up/down by a large factor may get the "jaggies".
A better way to resize your kinetic stage is to:
Resize the kinetic container element proportionally
Let Kinetic know its container is resizing with .setWidth and .setHeight
Use .setScaleX and setScaleY followed by stage.draw to scale the stage and all its components.
Here's an implemetation of the method recommended by markE. It also incorporates techniques described in this article on auto-resizing HTML5 games.
Find a working demo here: http://jsfiddle.net/klenwell/yLDVz/
var iPadLandscapeDimensions = {
width: 1024,
height: 768
};
var KineticScreenAutoSize = (function() {
var self = {
$container: null,
stage: null,
baseWidth: 0,
baseHeight: 0
};
var init = function(selector, baseWidth, baseHeight) {
self.$container = $(selector);
self.baseWidth = baseWidth;
self.baseHeight = baseHeight;
initStage();
};
var initStage = function($container) {
self.stage = new Kinetic.Stage({
container: self.$container.attr('id'),
width: self.baseWidth,
height: self.baseHeight
});
};
/*
* Screen-Sizing Methods
*/
var autoSize = function() {
// Style required for resizeScreen below
self.$container.css({
position: 'absolute',
left: '50%',
top: '50%',
width: '100%',
height: '100%'
});
// Resize automatically
window.addEventListener('resize', resizeStageToFitScreen, false);
window.addEventListener('orientationchange', resizeStageToFitScreen, false);
// Resize now
resized = resizeStageToFitScreen();
};
var resizeStageToFitScreen = function() {
/*
* Following directions here: https://stackoverflow.com/a/19645405/1093087
*/
var resized = calculateResize();
// Resize the kinetic container element proportionally
resized.cssSettings = {
left: resized.xToCenter + 'px',
top: resized.yToCenter + 'px',
width: resized.width,
height: resized.height,
}
self.$container.css(resized.cssSettings);
// Let Kinetic know its container is resizing with .setWidth and .setHeight
self.stage.setSize(resized);
// Use .setScaleX and setScaleY followed by stage.draw to scale the stage
// and all its components.
self.stage.scaleX(resized.xScale);
self.stage.scaleY(resized.yScale);
self.stage.draw();
return resized;
};
var calculateResize = function() {
var resized = {
width: 0,
height: 0,
xScale: 0,
yScale: 0,
xToCenter: 0,
yToCenter: 0
}
var windowWidth = window.innerWidth,
windowHeight = window.innerHeight,
desiredWidthToHeightRatio = self.baseWidth / self.baseHeight,
currentWidthToHeightRatio = windowWidth / windowHeight;
if ( currentWidthToHeightRatio > desiredWidthToHeightRatio ) {
resized.width = windowHeight * desiredWidthToHeightRatio;
resized.height = windowHeight;
}
else {
resized.width = windowWidth;
resized.height = windowWidth / desiredWidthToHeightRatio;
}
resized.xToCenter = (window.innerWidth - resized.width) / 2;
resized.yToCenter = (window.innerHeight - resized.height) / 2;
resized.xScale = resized.width/self.baseWidth,
resized.yScale = resized.height/self.baseHeight;
return resized;
};
/*
* Public API
*/
var publicAPI = {
init: init,
stage: function() { return self.stage },
autoSize: autoSize
}
return publicAPI;
})();
// Launch Resize
$(document).ready(function() {
KineticScreenAutoSize.init(
'div#stage-container',
iPadLandscapeDimensions.width,
iPadLandscapeDimensions.height
);
KineticScreenAutoSize.autoSize();
});

Categories