So here is what I'm trying to do:
Display a pattern that is randomly created with images, but keep getting the [object HTML Image Element] box appearing
var s1 = [];
s1[0] = new Image();
s1[0].src = '11.jpg';
s1[1] = new Image();
s1[1].src = '12.jpg';
s1[2] = new Image();
s1[2].src = '13.jpg';
s1[3] = new Image();
s1[3].src = '14.png';
s1[4] = new Image();
s1[4].src = '15.png';
var s2 = [];
s2[0] = new Image();
s2[0].src = '21.jpg';
s2[1] = new Image();
s2[1].src = '22.jpg';
s2[2] = new Image();
s2[2].src = '23.jpg';
s2[3] = new Image();
s2[3].src = '24.png';
s2[4] = new Image();
s2[4].src = '25.jpg';
var s3 = [];
s3[0] = new Image();
s3[0].src = '31.jpg';
s3[1] = new Image();
s3[1].src = '32.jpg';
s3[2] = new Image();
s3[2].src = '33.jepg';
s3[3] = new Image();
s3[3].src = '34.jpg';
s3[4] = new Image();
s3[4].src = '35.gif';
The arrays are now created, I want it to randomly pick 1 of the 5 to use, which seems to be working fine
p1 = Math.floor(Math.random() * 5 + 1);
p2 = Math.floor(Math.random() * 5 + 1);
p3 = Math.floor(Math.random() * 5 + 1);
if(p1 == 1) {
p1 = s1[0];
} else if (p1 == 2) {
p1 = s1[1];
} else if (p1 == 3) {
p1 = s1[2];
} else if (p1 == 4) {
p1 = s1[3];
} else if (p1 == 5) {
p1 = s1[4];
}
This exact same if/ else if structure is done for p2 and p3, I wont copy and paste it in to save space, now I want to output the final pattern, which is when i'm getting the object HTML image element appearing
while(repeat > 0){
pattern = p1 + p2 + p3 + pattern;
repeat --;
}
correctAnswer = 1;
$("#patternSpan").html(pattern)
Probelm
What does it mean to add images to each other?
Should all the rgb values for each pixel be added together? Should it create a new image with the pixels appended? appended to which side?
Neither of these things make sense for a language to automatically do, so whats happening here:
pattern = p1 + p2 + p3 + pattern;
Is that javascript is trying to decide if + means numeric addition or string concatenation.
Because the values aren't numbers, JS defaults to string concatenation.
What happens to a non-string when you attempt string concatenation?
JS calls .toString() on the value first
Turns out the string value for an image is [object HTML Image Element]
Solution
So what you want to do is append the images to #patternSpan. So instead of adding the images together, append them to the span:
$("#patternSpan").append(p1, p2, p3)
Side note
The section where you randomly pick the images can be cleaned up a lot by doing this instead:
idx1 = Math.floor(Math.random() * 5 + 1);
idx2 = Math.floor(Math.random() * 5 + 1);
idx3 = Math.floor(Math.random() * 5 + 1);
p1 = s1[idx1];
p2 = s2[idx2];
p3 = s3[idx3];
You should also avoid re-using variables like p1 for different types of things, ie storing a random number and storing an image.
You are more likely to run into type related bugs if a variable can contain multiple possible types.
Related
I'm not that new to using Flash, but I mostly used it for making animations and I don't really use actionscript much but this time I wanted to try making layouts by code.
I'm trying to position square movieclips to form a grid like menu.
I positioned the each of the movieclips all in code but I think perhaps there's a better and more efficient way of doing this.
The code is very basic but I'll post it anyway so you have an image of what I'm trying to make it look like. I'm not really good at explaining so, I apologize.
//1st row//
var btn1:MovieClip = new dBtn();
btn1.x = -210;
btn1.y = -90;
addChild(btn1);
var btn2:MovieClip = new dBtn();
btn2.x = btn1.x+70;
btn2.y = btn1.y;
addChild(btn2);
var btn3:MovieClip = new dBtn();
btn3.x = btn2.x+70;
btn3.y = btn2.y;
addChild(btn3);
//2nd row//
var btn4:MovieClip = new dBtn();
btn4.x = btn1.x;
btn4.y = btn1.y+70;
addChild(btn4);
var btn5:MovieClip = new dBtn();
btn5.x = btn4.x+70;
btn5.y = btn4.y;
addChild(btn5);
var btn6:MovieClip = new dBtn();
btn6.x = btn5.x+70;
btn6.y = btn5.y;
addChild(btn6);
//3rd row//
var btn7:MovieClip = new dBtn();
btn7.x = btn4.x;
btn7.y = btn4.y+70;
addChild(btn7);
var btn8:MovieClip = new dBtn();
btn8.x = btn7.x+70;
btn8.y = btn7.y;
addChild(btn8);
var btn9:MovieClip = new dBtn();
btn9.x = btn8.x+70;
btn9.y = btn8.y;
addChild(btn9);
Loops + simple math.
var buttonsList:Array = new Array;
for (var i:int = 0; i < 9; i++)
{
// You can omit () with "new" operator if there are no arguments.
var aBut:MovieClip = new dBtn;
// Value of i % 3 goes 0 1 2 0 1 2 0 1 2.
aBut.x = -210 + 70 * (i % 3);
// Value of int(i / 3) goes 0 0 0 1 1 1 2 2 2.
aBut.y = -90 + 70 * int(i / 3);
addChild(aBut);
buttonsList[i] = aBut;
}
Then to address each one of them you can use their indices from 0 to 8 respectively:
// Make the central one semi-transparent.
buttonsList[4].alpha = 0.5;
This code builds a palette of tiles for use in a map maker program. It takes in an array set by its parent and uses the bitmaps(from the objects) in that array to display a grid of tiles. Right now it only does a 5x5 grid, but what if there are more than 25 tiles in my tileSet? I want to display only the 5x5 tile grid, but be able to scroll through the images. I imagine that I need to make another rectangle to use as its mask and use a ScrollBar to make it scrollRect, but I can't get this working. Please Help.
public function Palette(X:uint, Y:uint, tileSet:Array)
{
addChild(handleGraphics);
var palette:Rectangle = new Rectangle(X, Y, 5*32, tileSet.length*32); //Default size is 5x5 tiles.
handleGraphics.DrawGrid(32,palette.x,palette.y,5,5);
var counter:int = 0;
for(var i:int = 0; i < 5; i++)
{
paletteArray[i] = [];
for(var u:int = 0; u < 5; u++)
{
if(counter >= tileSet.length)
{
counter = 0; //Which frame to show?
}
var b:Bitmap = new Bitmap(tileSet[counter].Graphic);
b.x = (palette.x) + 32 * u; //Align with palette Rectangle.
b.y = (palette.y) + 32 * i; ///////////////////////////////
addChild(b);
var tileObj:Object = new Object();
tileObj.Name = tileSet[counter].Name;
tileObj.Frame = tileSet[counter].Frame;
tileObj.Graphic = tileSet[counter].Graphic;
paletteArray[i].push(tileObj);
setChildIndex(b, 0); //Under grid.
counter++;
}
}
ActivatePaletteListeners();
}
This code works great for a tileSet array that has less than 25 objects. It loops and shows them continuously until it hits 25. I could do without this I guess, but it is a neat affect.
In another class (HandleTiles) I cycle through my tileSet MovieClip and use each frame to create a new object for each tile.
public function GetPaletteTiles(MC:MovieClip)
{
if (tileArray != null)
{
tileArray.length = 0;
}
for(var i:int = 1; i <= MC.totalFrames; i++)
{
MC.gotoAndStop(i); //Change frame for new info.
var tileObj:Object = new Object(); //The object to push to an array of tiles.
var graphicData:BitmapData = new BitmapData(32,32);
graphicData.draw(MC); //Graphic data from sampleTS.
tileObj.Name = MC.currentFrameLabel;
tileObj.Frame = MC.currentFrame;
tileObj.Graphic = graphicData;
tileArray.push(tileObj);
}
BuildIndexArray(15, 20); //Default size 15 x 20.
}
And here I set the tileSet to use
private function ChangeActiveTileset(Mc:MovieClip)
{
activeTileset = Mc;
GetPaletteTiles(activeTileset);
UpdatePalette();
}
I can change the tileSet with a comboBox. That's why I tear down the tileArray every time I call GetPaletteTiles(). Each tileSet is a different MovieClip, like Buildings, Samples, InTheCity, etc.
Sorry I didn't have time to get this code together earlier. Here's tiling code pieces. Because you're using rectangle and you have to stay under max dimensions you have to move the source mc. I think you already know everything else in there.
// set the bmp dimensions to device screensize to prevent exceeding device's max bmp dimensions
if (bStagePortrait) {
iTileWidth = Capabilities.screenResolutionX;
iTileHeight = Capabilities.screenResolutionY;
} else {
iTileWidth = Capabilities.screenResolutionY;
iTileHeight = Capabilities.screenResolutionX;
}
// mcList.mcListVector is the source mc - a regular mc containing mcs, jpgs, dynamic text, vector shapes, etc.
// mcList.mcListBmp is an empty mc
aListTiles = new Array();
iNumberOfTiles = Math.ceil(mcList.height / iTileHeight);
for (i = 0; i < iNumberOfTiles; i++) {
var bmpTile: Bitmap;
// move the source mc
mcList.mcListVector.y = -(i * iTileHeight);
bmpTile = fDrawTile(mcList, 0, 0, iTileWidth, iTileHeight);
mcList.mcListBmp.addChild(bmpTile);
bmpTile.x = 0;
bmpTile.y = (i * iTileHeight);
aListTiles.push(bmpTile);
}
// remove the regular mc
mcList.mcListVector.removeChild(mcList.mcListVector.mcPic);
mcList.mcListVector.mcPic = null;
mcList.removeChild(mcList.mcListVector);
mcList.mcListVector = null;
}
function fDrawTile(pClip: MovieClip, pX: int, pY: int, pWidth: int, pHeight: int): Bitmap {
trace("fDrawTile: " + pX + "," + pY + " " + pWidth + "," + pHeight);
var rectTemp: Rectangle = new Rectangle(pX, pY, pWidth, pHeight);
var bdClip: BitmapData = new BitmapData(pWidth, pHeight, true, 0x00000000);
var bdTemp: BitmapData = new BitmapData(pWidth, pHeight, true, 0x00000000);
bdClip.draw(pClip, null, null, null, rectTemp, true);
bdTemp.copyPixels(bdClip, rectTemp, new Point(0, 0));
var bmpReturn: Bitmap = new Bitmap(bdTemp, "auto", true);
return bmpReturn;
}
I have a randomly sorted array of, say, 3 items. Instead of displaying all 3 items in one dynamic text box (see code below), I'd like to display each item across 3 different text boxes. How might I go about doing this?
var Questions:Array = new Array;
Questions[0] = "<b><p>Where Were You Born?</p><br/>";
Questions[1] = "<b><p>What is Your Name?</p><br/>";
Questions[2] = "<b><p>When is Your Birthday?</p><br/>";
function randomize (a:*, b:*): int {
return (Math.random() > .5) ? 1: -1;
}
questions_txtbox.htmlText = Questions.toString() && Questions.join("");
The following code accomplishes what you were asking for, although the shuffling function is crude, it gets the job done. I also dynamically generated the three Text Fields as opposed to creating them on the stage and giving them unique instance names, so you will need to adjust the x/y coordinates for these new textfields as you see fit. I tested this on Flash CC 2014 and it worked properly.
import flash.text.TextField;
var Questions:Array = new Array();
Questions[0] = "<b><p>Where Were You Born?</p><br/>";
Questions[1] = "<b><p>What is Your Name?</p><br/>";
Questions[2] = "<b><p>When is Your Birthday?</p><br/>";
var shuffleAttempts:int = 10 * Questions.length;
var questionTextFields:Array = new Array(3);
function randomize (a:*, b:*): int {
return (Math.random() > .5) ? 1: -1;
}
function shuffleQuestions(arr:Array):void {
var temp:String;
for(var i:int = 0; i < shuffleAttempts; i++ ) {
var randIndex1:int = Math.floor(Math.random() * Questions.length);
var randIndex2:int = Math.floor(Math.random() * Questions.length);
if( randIndex1 != randIndex2 ) {
temp = Questions[randIndex1];
Questions[randIndex1] = Questions[randIndex2];
Questions[randIndex2] = temp;
}
}
}
shuffleQuestions(Questions); // shuffle question list
for( var questionIndex:int = 0; questionIndex < 3; questionIndex++ ) {
if( questionIndex < Questions.length ) {
var questionField = new TextField(); // create new text field
questionField.htmlText = Questions[questionIndex]; // take a question from the questions list and set the text fields text property
questionField.y = questionIndex * 20; // move the text field so that it does not overlap another text field
questionField.autoSize = "left"; // autosize the text field to ensure all the text is readable
questionTextFields[questionIndex] = questionField; // store reference to question textfield instance in array for later use.
addChild(questionField); // add textfield to stage
}
}
I'm a bit new to canvas and such so forgive if it's a trivial question.
I'd like to be able to animate an object following a path (defined as bezier path) but I'm not sure how to do it.
I've looked at Raphael but I can't work out how to follow the path over time.
Cake JS looked promising in the demo, but I'm really struggling the documentation, or lack thereof in this case.
Has anyone got some working example of this?
Use the code on my website from this related question, but instead of changing the .style.left and such in the callback, erase and re-draw your canvas with the item at the new location (and optionally rotation).
Note that this uses SVG internally to easily interpolate points along a bézier curve, but you can use the points it gives you for whatever you want (including drawing on a Canvas).
In case my site is down, here's a current snapshot of the library:
function CurveAnimator(from,to,c1,c2){
this.path = document.createElementNS('http://www.w3.org/2000/svg','path');
if (!c1) c1 = from;
if (!c2) c2 = to;
this.path.setAttribute('d','M'+from.join(',')+'C'+c1.join(',')+' '+c2.join(',')+' '+to.join(','));
this.updatePath();
CurveAnimator.lastCreated = this;
}
CurveAnimator.prototype.animate = function(duration,callback,delay){
var curveAnim = this;
// TODO: Use requestAnimationFrame if a delay isn't passed
if (!delay) delay = 1/40;
clearInterval(curveAnim.animTimer);
var startTime = new Date;
curveAnim.animTimer = setInterval(function(){
var now = new Date;
var elapsed = (now-startTime)/1000;
var percent = elapsed/duration;
if (percent>=1){
percent = 1;
clearInterval(curveAnim.animTimer);
}
var p1 = curveAnim.pointAt(percent-0.01),
p2 = curveAnim.pointAt(percent+0.01);
callback(curveAnim.pointAt(percent),Math.atan2(p2.y-p1.y,p2.x-p1.x)*180/Math.PI);
},delay*1000);
};
CurveAnimator.prototype.stop = function(){
clearInterval(this.animTimer);
};
CurveAnimator.prototype.pointAt = function(percent){
return this.path.getPointAtLength(this.len*percent);
};
CurveAnimator.prototype.updatePath = function(){
this.len = this.path.getTotalLength();
};
CurveAnimator.prototype.setStart = function(x,y){
var M = this.path.pathSegList.getItem(0);
M.x = x; M.y = y;
this.updatePath();
return this;
};
CurveAnimator.prototype.setEnd = function(x,y){
var C = this.path.pathSegList.getItem(1);
C.x = x; C.y = y;
this.updatePath();
return this;
};
CurveAnimator.prototype.setStartDirection = function(x,y){
var C = this.path.pathSegList.getItem(1);
C.x1 = x; C.y1 = y;
this.updatePath();
return this;
};
CurveAnimator.prototype.setEndDirection = function(x,y){
var C = this.path.pathSegList.getItem(1);
C.x2 = x; C.y2 = y;
this.updatePath();
return this;
};
…and here's how you might use it:
var ctx = document.querySelector('canvas').getContext('2d');
ctx.fillStyle = 'red';
var curve = new CurveAnimator([50, 300], [350, 300], [445, 39], [1, 106]);
curve.animate(5, function(point, angle) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillRect(point.x-10, point.y-10, 20, 20);
});
In action: http://jsfiddle.net/Z2YSt/
So, here is the verbose version:
t being any number between 0 and 1 representing time; the p0, p1, p2, p3 objects are the start point, the 1st control point, the 2nd control point an the end point respectively:
var at = 1 - t;
var green1x = p0.x * t + p1.x * at;
var green1y = p0.y * t + p1.y * at;
var green2x = p1.x * t + p2.x * at;
var green2y = p1.y * t + p2.y * at;
var green3x = p2.x * t + p3.x * at;
var green3y = p2.y * t + p3.y * at;
var blue1x = green1x * t + green2x * at;
var blue1y = green1y * t + green2y * at;
var blue2x = green2x * t + green3x * at;
var blue2y = green2y * t + green3y * at;
var finalx = blue1x * t + blue2x * at;
var finaly = blue1y * t + blue2y * at;
Here is a ball using <canvas> following a path in JSfiddle
The names of the variables come from this gif wich is the best explication for bezier curves: http://en.wikipedia.org/wiki/File:Bezier_3_big.gif
A short version of the code, inside a function ready to copy/paste:
var calcBezierPoint = function (t, p0, p1, p2, p3) {
var data = [p0, p1, p2, p3];
var at = 1 - t;
for (var i = 1; i < data.length; i++) {
for (var k = 0; k < data.length - i; k++) {
data[k] = {
x: data[k].x * at + data[k + 1].x * t,
y: data[k].y * at + data[k + 1].y * t
};
}
}
return data[0];
};
Related stuff:
http://blogs.sitepointstatic.com/examples/tech/canvas-curves/bezier-curve.html
http://13thparallel.com/archive/bezier-curves/
http://gsgd.co.uk/sandbox/jquery/easing/jquery.easing.1.3.js
http://www.youtube.com/watch?v=hUCT4b4wa-8
I wouldn't use Canvas for this unless you really have to. SVG has animation along a path built in. Canvas requires quite a bit of math to get it working.
Here's one example of SVG animating along a path.
Here's some discussion about it for raphael: SVG animation along path with Raphael
Please note that Raphael uses SVG and not HTML5 Canvas.
One way to animate along a bezier path in Canvas is to continuously bisect the bezier curve, recoring the midpoints until you have a lot of points (say, 50 points per curve) that you can animate the object along that list of points. Search for bisecting beziers and similar queries for the related math on that.
I have a MovieClip holding an irregular shape such as this one:
I need to generate a random point on this shape.
I can use brute force by generating points within the bounding box and then hitTesting to see if they reside on the irregular shape. However, I'm sure there's a more efficient way to tackle this problem.
What is the most efficient way to generate a random point on an irregular shape?
You mentioned hitTest, but I assume you meant hitTestPoint().
If so, a function go get the random points you mention, would look a bit like this:
function getRandomPointsInClip(target:MovieClip,numPoints:int):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>(numPoints,true);
var width:Number = target.width,height:Number = target.height;
for(var i:int = 0; i < numPoints ; i++){
var point:Point = new Point(target.x+Math.random() * width,target.y+Math.random() * height);
if(target.hitTestPoint(point.x,point.y,true)) points[i] = point;//is the random coord inside ?
else i = i-1;//nope, go back one step - > retry above until it is inside
}
return points;
}
The other I hinted at in my comment involves looping through non transparent pixels in a bitmap data of your object. This method would insure you don't have many duplicates, as opposed to the previous method, but it also means, you have less control over the number of points created and there's extra memory used for creating the bitmap. Still, for documentation purposes, here is the function:
function getGridPointsInClip(target:MovieClip,res:int,offset:Number = 3):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>();
var x:int,y:int,alpha:int,w:int = int(target.width),h:int = int(target.height);
var bmd:BitmapData = new BitmapData(w,h,true,0x00FFFFFF);bmd.draw(target);
var pixels:Vector.<uint> = bmd.getVector(bmd.rect),numPixels:int = w*h;
for(var i:int = 0; i < numPixels; i+=res) {
x = i%bmd.width;
y = int(i/bmd.width);
alpha = pixels[i] >>> 24;
if(alpha > 0) points.push(new Point(x+random(-offset,offset),y+random(-offset,offset)));
}
return points;
}
function random(from:Number,to:Number):Number {
if (from >= to) return from;
var diff:Number = to - from;
return (Math.random()*diff) + from;
}
And here'a very basic test:
var pts:Vector.<Point> = getRandomPointsInClip(mc,300);
//var pts:Vector.<Point> = getGridPointsInClip(mc,100,4);
for(var i:int = 0 ; i < pts.length; i++) drawCircle(pts[i].x,pts[i].y,3,0x009900);
function getRandomPointsInClip(target:MovieClip,numPoints:int):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>(numPoints,true);
var width:Number = target.width,height:Number = target.height;
for(var i:int = 0; i < numPoints ; i++){
var point:Point = new Point(target.x+Math.random() * width,target.y+Math.random() * height);
if(target.hitTestPoint(point.x,point.y,true)) points[i] = point;//is the random coord inside ?
else i = i-1;//nope, go back one step - > retry above until it is inside
}
return points;
}
function getGridPointsInClip(target:MovieClip,res:int,offset:Number = 3):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>();
var x:int,y:int,alpha:int,w:int = int(target.width),h:int = int(target.height);
var bmd:BitmapData = new BitmapData(w,h,true,0x00FFFFFF);bmd.draw(target);
var pixels:Vector.<uint> = bmd.getVector(bmd.rect),numPixels:int = w*h;
for(var i:int = 0; i < numPixels; i+=res) {
x = i%bmd.width;
y = int(i/bmd.width);
alpha = pixels[i] >>> 24;
if(alpha > 0) points.push(new Point(x+random(-offset,offset),y+random(-offset,offset)));
}
return points;
}
function random(from:Number,to:Number):Number {
if (from >= to) return from;
var diff:Number = to - from;
return (Math.random()*diff) + from;
}
function drawCircle(x:Number,y:Number,radius:Number,color:uint):void{
graphics.lineStyle(1,color);
graphics.drawCircle(x-radius,y-radius,radius);
}
HTH
If you think of some non-blob like shapes, it's clear the check random pixel, try again method isn't really a good way. The bounding box area could be huge compared to the shape area.
What you could do to improve the effectiveness is getting a vector of the BitmapData of the shape. It should contain all pixels of the bounding box. Update - it would be nice now if we could pick a random point, and remove it from the vector if it isn't inside the shape. Unfortunately the vector only contains the pixels' colour, not the position which is implicit and only correct if we don't change the vector's length. Since we don't need to know the actual colour, we can omit all transparent pixels and store an inside pixel's position as it's value in the vector. This way we don't need to create a new object for each pixel of the shape (that would be quite expensive!).
var v:Vector.<uint> shapeBoxBitmap.getVector(shapeBoxBitmap.rect);
var pixelNum:int = v.length;
for(var i:uint = 0; i < pixelNum; i++) {
if( v[i] && 0xFF000000 == 0) { // transparent pixel, outside off shape
v.splice(i,1);
} else {
v[i] = i;
}
}
//get random point
var randomPixel:int = v[Math.floor(Math.random()*v.length)];
var point:Point = new Point(randomPixel%shapeBitmap.width,int(randomPixel/shapeBitmap.width));