If I don't cache the container of tiles after creating the map, I can see them rendered to the canvas:
function createWorld() {
background = new createjs.Container();
for (var y = 0; y < mapWidth; y++) {
for (var x = 0; x < mapHeight; x++) {
var tile = new createjs.Bitmap('images/tile.png');
tile.x = x * 28;
tile.y = y * 30;
background.addChild(tile);
}
}
//background.cache(0, 0, mapWidth, mapHeight);
stage.addChild(background);
}
If I do cache the background container of tile children, it won't render
function createWorld() {
background = new createjs.Container();
for (var y = 0; y < mapWidth; y++) {
for (var x = 0; x < mapHeight; x++) {
var tile = new createjs.Bitmap('images/tile.png');
tile.x = x * 28;
tile.y = y * 30;
background.addChild(tile);
}
}
background.cache(0, 0, mapWidth, mapHeight);
stage.addChild(background);
}
Why?
This is probably because that even when preloaded, images may not be available synchronously if you use a path to create them. If you aren't caching them, then they wouldn't show up immediately when the stage is updated -- but if you ticked the stage, they might appear to load instantly (they just take a frame or so).
I recommend you preload them, either internally, or using something like PreloadJS. Then pass the loaded instance to the bitmaps instead.
var img = document.createElement("img");
img.onload = draw;
img.src = "images/tile.png";
function draw() {
// loop
var bmp = new createjs.Bitmap(img); // Use a reference instead of a path
}
Note that this also reduces overhead, since only one image is created, and all the Bitmaps share a reference to it.
If all your tiles are the same, you might want to look at the Graphics.beginBitmapFill() method. http://www.createjs.com/Docs/EaselJS/classes/Graphics.html#method_beginBitmapFill
Related
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'm building a Tile Viewer in Adobe AIR and so far I have implemented the Drag and Drop function that allows the user to load PNG and JPG files.
The function returns a ':File' format, but how can i gram the ':Bitmap' data and place the image in an existing movieclip while looping and make that one tile image repeat in x and y so a preview of a tilemap will shown ?
my thought was to simply add instances of that one file, but i dont know the exact code for that.
( create a Loader, pass the File format to that, and get the BitmapData and place it in place by defining position x and y and also defining width and height. also i want to make instances, to the code doesnt load the image 10 x 10 times )
i would be glad for all helpfull answers.
Edit:
example theory:
( without correct syntax )
image = new ImageFromDropBox(); // bitmap or whatever
for( x = 0; x < 10; x++) {
for( y = 0; y < 10; y++) {
image.x = tilesize * x;
image.y = tileSize * y;
image.width = tileSize;
image.height = tileSize;
stage.addChild( image ); // so each of the 100 places images should be just instances of the ONE LOADED IMAGE
}
}
When working with the File class, you can use the fileInstance.url parameter to load said file in a loader:
var loader:Loader = new Loader();
var urlReq:URLRequest = new URLRequest(file.url);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaded);
loader.load(urlReq);
function loaded(e:Event):void {
var bmp:Bitmap = loader.content as Bitmap
//scale it down and create a scaled down bitmap data for more efficient memory usage
bmp.width = tileSize;
bmp.height = tileSize;
var bData:BitmapData = new BitmapData(tileSize,tileSize); //your new bitmap data
bData.draw(bmp); //capture the scaled down version of the bitmap
//if you don't need the original anymore, dispose of it
bmp.bitmapData.dispose();
var image:Bitmap;
for( var x:int = 0; x < 10; x++) {
for( var y:int = 0; y < 10; y++) {
image = new Bitmap(bmp.bData);
image.x = tilesize * x;
image.y = tileSize * y;
image.width = tileSize;
image.height = tileSize;
stage.addChild( image );
}
}
}
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));
I'm making an image gallery and I want to have a bunch of thumbnails on the bottom of the screen that smoothly slide from side to side when the mouse moves.
I'm working with a custom class for the container (Tiles) and a custom class for the thumbnails (ImageIcon).
I have a ComboBox which allows users to to choose a gallery. When the user chooses a gallery, the following code is run and the thumbnails should reload. The problem here is that the icons appear on top of each other instead of side by side, also switching categories should remove the old one (see the first for loop), but it does not. Also, the Icons are not animating properly. The animation code is below as well. Right now, only one of the icons will move. The icons should move in order from side to side, stopping when the last few icons hit the edge of the screen, so that they don't get "lost" somewhere off to the side.
Gallery Loader Code:
public function loadCategory(xml:XML = null) {
if (xml != null) {
dp = new DataProvider(xml);
for (var k:int = 0; k < this.numChildren; k++) {
this.removeChild(this.getChildAt(k));
}
var black:DropShadowFilter = new DropShadowFilter(0, 45, 0x000000, 1, 3, 3, 1, 1);
var white:DropShadowFilter = new DropShadowFilter(0, 45, 0xFFFFFF, 1, 2, 2, 1, 1);
for (var i:int = 0; i < dp.length; i++) {
var imgicon:ImageIcon = new ImageIcon();
imgicon.addEventListener(MouseEvent.CLICK, showImage);
imgicon.width = 100;
imgicon.x = (i * (imgicon.width + 20));
imgicon.path = dp.getItemAt(i).data;
imgicon.loadIcon();
imgicon.filters = [black, white];
stage.addEventListener(Event.ENTER_FRAME, moveIcon);
this.addChild(imgicon);
}
} else {
//Failed to load XML
}
}
Icon Animation Code:
public function moveIcon(e:Event){
var speed = 0;
speed = Math.floor(Math.abs(this.mouseX/20));
var image = this.getChildAt(k);
var imagebox = image.width + 20;
var edge:Number = (800/2);
if (this.mouseX > 0) {
for (var k:int = 0; k < this.numChildren; k++) {
if (image.x - (imagebox/2) + speed < -edge + (k * imagebox)) {
speed = 0;
}
image.rotationY = Math.floor(image.x/20);
image.x -= Math.floor(speed);
}
} else {
for (var j = this.numChildren; j >= 0; j--) {
if (image.x + speed > edge - ((imagebox * j) )) {
speed = 0;
}
image.rotationY = Math.floor(image.x/20);
image.x += Math.floor(speed);
}
}
}
As I see it, you have three questions (You should have put these at the end of your question instead of "What is wrong with my code?"). One of the main principles of programming is breaking problems into smaller parts.
How do I line up the ImageIcon beside each other?
How do I remove the old ImageIcon, when switching categories?
How do I animate ALL the ImageIcons together, based on the mouse position, with constraints on either side?
Question 1
I can't see anything wrong, but just check that when you are setting imgicon.x, that imgicon.width is actually set.
Question 2
Instead of relying on numChildren and getChildAt(), I would create a currentIcons array member variable, and when you create a new ImageIcon, just push it onto the array. Then when you want to remove them, you can just loop through the array like this:
for each (var cIcon:ImageIcon in currentIcons)
{
cIcon.removeEventListener(MouseEvent.CLICK, showImage);
removeChild(cIcon);
}
currentIcons = [];
As you can see, I am also removing any listeners that I have added. This is best practice. Then clearing the array when I have removed all the icons.
Question 3
I can see a few things wrong with your code. First, in the line where image is set, k is not set yet!
Here you can also use the currentIcons array, but you probably can't use a for each in loop, because that gives you the items out of order. Just a normal for loop will be better.
I haven't tested this code for the moveIcon method, but the idea should work. You may have to tweek it though:
public function moveIcon(e:Event):void
{
var speed:Number = Math.floor(this.mouseX / 20); // Removed "abs".
var imageBox:Number = currentIcons[0].width;
var edge:Number = 800 / 2;
for (var i:int = 0; i < currentIcons.length; i++)
{
var image:ImageIcon = currentIcons[i] as ImageIcon;
image.x += speed;
image.rotationY = Math.floor(image.x / 20);
var min:int = -edge + (i * imagebox);
if (image.x < min) image.x = min;
var max:int = edge - (imagebox * i);
if (image.x > max) image.x = max;
}
}
EDIT* Sorry, it was supposed to be a greater than in the last if statement, but I had a less than by accident.
I'm trying to make a dynamic image gallery from and xml. From my tutorials, right now i've got it so it will constantly add the next thumbnail below the other, which is fine, but I'm trying to figure out how to make it that once it reaches a certain y coordinate, it will move the x coordinate over and stack them again. So that rather one long list of thumbs, it will be a side by side stack. For some reason, I can't get it in my head how something like this would work. My goal is to have a side by side stack that I will end up putting in a movie clip that will be masked to show only 2 stacks at a time. Then when clicking a button will slide it over. I was planning to use the "movieclip.length" to calculate how far over to move it, but i haven't gotten that far yet. This is what I got:
var gallery_xml:XML;
var xmlReq:URLRequest = new URLRequest("xml/content.xml");
var xmlLoader:URLLoader = new URLLoader();
var imageLoader:Loader;
function xmlLoaded(event:Event):void
{
gallery_xml = new XML(xmlLoader.data);
info_txt.text = gallery_xml.screenshots.image[0].attribute("thumb");
for(var i:int = 0; i < gallery_xml.screenshots.image.length(); i++)
{
imageLoader = new Loader();
imageLoader.load(new URLRequest(gallery_xml.screenshots.image[i].attribute("thumb")));
imageLoader.x = 0;
imageLoader.y = i * 70 + 25;
imageLoader.name = gallery_xml.screenshots.image[i].attribute("src");
addChild(imageLoader);
imageLoader.addEventListener(MouseEvent.CLICK, showPicture);
}
}
xmlLoader.load(xmlReq);
xmlLoader.addEventListener(Event.COMPLETE, xmlLoaded);
function showPicture(event:MouseEvent):void
{
imageLoader = new Loader();
imageLoader.load(new URLRequest(event.target.name));
imageLoader.x = 200;
imageLoader.y = 25;
addChild(imageLoader);
}
I can't seem to wrap my head around what I can do to move things over dynamically without having to write a custom if for each set of positions.. I get the feeling I've totally forgotten how to do algebra.
I'd suggest one of the following two solutions (untested):
var next_x:Number = 0.0;
var next_y:Number = 25.0;
for (var i:int = 0; i < gallery_xml.screenshots.image.length(); ++i)
{
// ...
imageLoader.y = next_y;
imageLoader.x = next_x;
next_y += 70;
if (next_y > THRESHOLD)
{
next_x += X_OFFSET;
next_y = 25.0;
}
}
That is, just keep track of where your next image will be placed and adjust that coordinate accordingly when you exceed thresholds. If you want to use your i variable, you'll have to calculate what row and column the image will be placed at:
for (var i:int = 0; i < gallery_xml.screenshots.image.length(); ++i)
{
// ...
var row:int = i % MAX_ITEMS_PER_COLUMN;
var column:int = i / MAX_ITEMS_PER_COLUMN;
imageLoader.y = 25 + row * 70;
imageLoader.x = column * COLUMN_WIDTH;
}