I have an Avatar class that extends the Sprite class. I also have a Room class that contains a Bitmap of "walkable" areas and non-walkable areas, as well as the room's artwork itself.
I need to make sure that the user can only walk on the white/transparent parts of the room "mask," without actually showing the black and white mask. What functions can I use to ensure that the user doesn't go into the black parts of the mask, quickly?
You just need to have this 'walkable' bitmap in memory
private const ALLOWANCE:Number = .1;
private function isTurnAllowed(maskBMD:BitmapData, position:Point):Boolean
{
//0xAARRGGBB
var color32:uint = maskBMD.getPixel32(int(position.x), int(position.y));
var alpha:uint = color32 >> 24 & 0xff;
var red:uint = color32 >> 16 & 0xff;
var green:uint = color32 >> 8 & 0xff;
var blue:uint = color32 & 0xff;
var color24:uint =color32 >> 8 & 0xffffff;
/*
if (alpha == 0 || color24 == 0xffffff) return true
strictly speaking this string is enough but in real your bitmap mask after resampling can have some interpolation artifacts so you need some allowance to pass not strictly white.
*/
var absoluteLightness:Number = red + green + blue + (0xff - alpha);//transparent is 0x00
var maximalLight:Number = 0xff * 4;
var lightness:Number = absoluteLightness / maximalLight;
if (lightness > 1 - ALLOWANCE)
return true
else
return false
}
Lets say your Avatar:Sprite is properly aligned around its own (0,0) point. Then you create a Walkable:Sprite with a shape of walkable areas. It must be attached to the display list, but not necessarily visible, you can set Walkable.visible = false.
function moveBy(dx:Number, dy:Number):void
{
var aPoint:Point = new Point();
aPoint.x = Avatar.x + dx;
aPoint.y = Avatar.y + dy;
// hitTestPoint works with Stage coordinates.
aPoint = Avatar.parent.localToGlobal(aPoint);
if (Walkable.hitTestPoint(aPoint.x, aPoint.y, true))
{
Avatar.x += dx;
Avatar.y += dy;
}
}
This code is very simple, it just disallows to move your Avatar out of Walkable map.
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;
}
Under the "Fill and Stroke" option in Flash, you can color a shape as one of the textures from your library. Example: http://i1.minus.com/iban9aRUCI7UTf.png
How can I do this with AS3 code?
If you're drawing with the Graphics API, you can use beginBitmapFill.
Open the properties of an image in your library and check "Export for ActionScript" on the ActionScript tab. Give it a Class name such as "MyTexture", and then you can use it like this:
var texturedCircle:Sprite = new Sprite();
texturedCircle.graphics.beginBitmapFill(new MyTexture());
texturedCircle.graphics.drawCircle(0, 0, 100);
You can approach this in two steps.
Calculate the average color from bitmap values. The function below (written by Soulwire) does this "by looping through the pixels in a BitmapData object, adding up all of the red, green and blue values, dividing them by the total number of pixels and then creating a new colour from the results."
function averageColour(source:BitmapData):uint {
var red:Number = 0;
var green:Number = 0;
var blue:Number = 0;
var count:Number = 0;
var pixel:Number;
for (var x:int = 0; x < source.width; x++) {
for (var y:int = 0; y < source.height; y++) {
pixel = source.getPixel(x, y);
red += pixel >> 16 & 0xFF;
green += pixel >> 8 & 0xFF;
blue += pixel & 0xFF;
count++
}
}
red /= count;
green /= count;
blue /= count;
return red << 16 | green << 8 | blue;
}
Use ColorTransform to apply it.
function setColor(obj:Object, color:uint, alpha:Number = 1):void {
/* Colors the object using uint */
// Pull the individual primaries
var r:Number = (color >> 16 ) & 0xFF;
var g:Number = (color >> 8) & 0xFF;
var b:Number = color & 0xFF;
obj.transform.colorTransform = new ColorTransform(0,0,0,alpha,r,g,b,0);
}
I've read a number of similar questions to this on here, but unfortunately none of them seem to give the exact answer I'm after, or they might but the maths is beyond me!
I'm creating a game where you have a cannon at the left edge of the screen. I want to be able to fire a cannonball from the cannon in an arc so that it intersects where the mouse pointer is on the screen.
I've seen a few examples that move a projectile in an arc from point a to point b, but what I need is for the cannonball to first move along the axis of the cannon itself, it's no good if the ball leaves the end of the cannon at a different angle to which the cannon is pointing.
The only force acting on the ball will be gravity and it's starting velocity.
Also to complicate matters, I need the cannons angle to change according to how far away the mouse pointer is from the end of the cannon, so if the pointer is far away than the cannon will point upwards say at an angle of 45 degrees, but if the pointer is very close to the end of the cannon then the cannon will point directly at the pointer, this I've more or less already got working by just getting the distance between them and then dividing it by a number and subtracting it from the rotation value of the cannon, but it's a bit of a rough way of doing it.
EDIT
Using the code below I've managed to the line in the screen shot below. But as you can see it's not the trajectory I need, I need something more like the red line I've put in.
And here's how I've implemented the code (probably wrongly)
public class GameTurretLine2
{
var rt:Object = null;
var lineMc:MovieClip = new MovieClip();
var myTurret:GameMainGun = null;
var pta:Point = new Point(0,0);
var ptb:Point = new Point(0,0);
var ptc:Point = new Point(0,0);
var ptd:Point = new Point(0,0);
public function GameTurretLine2(rt2,turret)
{
rt = rt2;
myTurret = turret;
lineMc.graphics.lineStyle(2, 0x55aa00);
mainLoop();
rt.rt.GameLayers.turretLineMc.addChild(lineMc);
}
function mainLoop()
{
lineMc.graphics.clear();
//get points
var turretEnd:Object = myTurret.rt.Useful.localToGlobalXY(myTurret.mC.turret.firePoint);
var turretStart:Object = myTurret.rt.Useful.localToGlobalXY(myTurret.mC.turret);
var mousePos:Point = new Point(myTurret.rt.rt.mouseX,myTurret.rt.rt.mouseY);
var inbetween:Point = new Point(0,0);
//start
pta.x = turretStart.newX;
pta.y = turretStart.newY;
//mouse end
ptd.x = mousePos.x;
ptd.y = mousePos.y;
// The cannon's angle:
// make the cannon's angle some inverse factor
// of the distance between the mouse and cannon tip
var dist:Number = myTurret.rt.Useful.getDistance(turretEnd.newX, turretEnd.newY, mousePos.x, mousePos.y);
var cAng:Number = dist * (180/Math.PI);
var ptbc:Point = new Point((ptd.x - pta.x) *.5,0);
ptbc.y = Math.tan(cAng) * ptbc.x;
//ptb = new Point(ptbc.x - ptbc.x * .15, ptbc.y);
ptb = new Point(turretEnd.newX, turretEnd.newY);
ptc = new Point(ptbc.x + ptbc.x * .5, ptbc.y);
// create the Bezier:
var bz:BezierSegment = new BezierSegment(pta,ptb,ptc,ptd);
trace(bz);
// define the distance between points that you want to draw
// has to be between 0 and 1.
var stepVal:Number = .1;
var curPt:Point = pta;
//draw circles
lineMc.graphics.drawCircle(pta.x, pta.y, 4);
lineMc.graphics.drawCircle(ptb.x, ptb.y, 4);
lineMc.graphics.drawCircle(ptc.x, ptc.y, 4);
lineMc.graphics.drawCircle(ptd.x, ptd.y, 4);
lineMc.graphics.lineStyle(2, 0x0000ff);
//step along the curve to draw it
for(var t:Number = 0;t < 1;t+=stepVal){
lineMc.graphics.moveTo(curPt.x, curPt.y);
curPt = bz.getValue(t);
trace("curPt = " + curPt.x + "," + curPt.y);
lineMc.graphics.lineTo(curPt.x, curPt.y);
}
trace("pta = " + pta.x + "," + pta.y);
trace("ptb = " + ptb.x + "," + ptb.y);
trace("ptc = " + ptc.x + "," + ptc.y);
trace("ptd = " + ptd.x + "," + ptd.y);
}
}
Also for some strange reason, the line created by the code flips, from how it is in the screen shot to an indented code (y flipped) just by moving the mouse a tiny amount, so as you move the mouse the line jumps everywhere.
One method is to create a Bezier curve.
This sounds like a workable solution because you essentially want the curve to always fit under some triangle. If this triangle defines the control points for a Bezier curve, you can make that match pretty closely the arc of a cannonball under gravity (it's not a perfect representation of gravity). One side-effect of this method is that the (inversed) height can define the force of the cannonball.
You can use the fl.motion.BezierSegment to create a curve and step along it. Paste this code into an FLA:
import fl.motion.BezierSegment;
var mySprite:Sprite = new Sprite();
addChild(mySprite);
mySprite.graphics.lineStyle(2, 0x55aa00);
// End point of the cannon:
var pta:Point = new Point(0, 100);
mySprite.graphics.drawCircle(pta.x, pta.y, 4);
trace("pta = " + pta.x + "," + pta.y);
// mouse point
// var ptd:Point = new Point(mouseX, mouseY);
// for testing:
var ptd:Point = new Point(200,100);
mySprite.graphics.drawCircle(ptd.x, ptd.y, 4);
trace("ptd = " + ptd.x + "," + ptd.y);
// The cannon's angle:
// make the cannon's angle some inverse factor
// of the distance between the mouse and cannon tip
// var dx:Number = ptd.x-pta.x;
// var dy:Number = ptd.y-pta.y;
// var dist:Number = Math.sqrt(dx * dx + dy * dy);
var cAng:Number = 30 * /(180/Math.PI);
// point the cannon in the correct direction here, however you are intending to do that.
// triangulate the cannon pt and mouse pt assuming the cannon's angle for both:
// *** NOTE: for simplicity, this assumes a straight line on the x-plane. ***
var ptbc:Point = new Point((ptd.x - pta.x) *.5,0);
ptbc.y = Math.tan(cAng) * ptbc.x;
trace("ptbc = " + ptbc.x + "," + ptbc.y);
// to adjust the curve:
var ptb:Point = new Point(ptbc.x - ptbc.x * .15, ptbc.y);
var ptc:Point = new Point(ptbc.x + ptbc.x * .5, ptbc.y);
mySprite.graphics.drawCircle(ptb.x, ptb.y, 4);
mySprite.graphics.drawCircle(ptc.x, ptc.y, 4);
// create the Bezier:
var bz:BezierSegment = new BezierSegment(pta,ptb,ptc,ptd);
trace(bz);
// define the distance between points that you want to draw
// has to be between 0 and 1.
var stepVal:Number = .1;
var curPt:Point = pta;
mySprite.graphics.lineStyle(2, 0x0000ff);
//step along the curve to draw it
for(var t:Number = 0;t < 1;t+=stepVal){
mySprite.graphics.moveTo(curPt.x, curPt.y);
curPt = bz.getValue(t);
trace("curPt = " + curPt.x + "," + curPt.y);
mySprite.graphics.lineTo(curPt.x, curPt.y);
}
mySprite.x = stage.stageWidth/2-mySprite.width/2;
mySprite.y = stage.stageHeight/2-mySprite.height/2;
As is, this code is not attached directly to the mouse, so you will have to use your own MouseEvent and AdjustCannonEvent to run this code. (Also, make sure to see the note in the code.)
I am a bit new to using blitting for graphics. But I have worked up a few demos myself, and I have been reading a lot of information on the methods used.
One common theme I have been seeing though is that all of them brute force rendering; drawing the farthest back object first and stepping through all other objects. even drawing objects that are going to be completely overlapped.
The reason all of them say this is that any kind of testing to see what should be drawn actually takes more time than just drawing everything with no checks.
Is there any kind of way to detect what should be drawn, that will run faster than just drawing everything?
It is hard to actually tell whether it is faster to check for what should be drawn or drawing stuff. You could maybe use both like If there is more than 5 images, use draw check, if not, draw them all. ...
So - the draw them all method is very obvious, and about the draw check it is like:
// drawCheck
var w:int = 300;
var h:int = 200;
var result:Bitmap = new Bitmap(w, h);
for (var x:int = 0; x < w; x++){
for (var y:int = 0; y < h; y++){
result.bitmapData.setPixel32(x, y, 0x00FFFFFF);
for (var iid:int = 0; iid < images.length; iid++){
var resC:uint = result.bitmapData.getPixel32(x, y);
var resA:uint = resC >>> 24;
var resR:uint = resC >>> 16 & 0xFF;
var resG:uint = resC >>> 8 & 0xFF;
var resB:uint = resC & 0xFF;
if (resA == 0xFF){
break;
}
var oriC:uint = images[iid].bitmapData.getPixel32(x, y);
var oriA:uint = oriC >>> 24 &;
var oriR:uint = oriC >>> 16 & 0xFF;
var oriG:uint = oriC >>> 8 & 0xFF;
var oriB:uint = oriC & 0xFF;
var newA:uint = resA + oriA;
var newR:uint = (256 / resA) * resR + (256 / oriA) * oriR;
var newG:uint = (256 / resA) * resR + (256 / oriA) * oriG;
var newB:uint = (256 / resA) * resR + (256 / oriA) * oriB;
var newC:uint = newA << 24 | newR << 16 | newG << 8 | newB;
result.bitmapData.setPixel32(x, y, newC);
}
}
}
Basically, MAYBE the drawing could be faster, but I am not sure - bitwise operations... Anyways - you should get the idea - this loops through X and Y coordinates and finally through the images.
Note: The images are stored like: 0 = front, images.length - 1 = back
It checks (HERE) if the resulting bitmap is already fully drawn by checking the alpha (if it equals 0xFF, there is no use of drawing), and if it is not, it merges the colours and adds the alpha.
You should do some performance tests so we know what is faster and when...
i've created a bitmap with data and placed it into a sprite so to receive mouse events. however, i'm struggling with reading the BitmapData within the sprite.
function showBitmapData(e:Event):void
{
var bData:BitmapData = new BitmapData(video.width, video.height);
bData.draw(video);
var bmap:Bitmap = new Bitmap(bData);
bmap.x = 220;
bmap.y = 20;
bmap.scaleX = bmap.scaleY = 2;
canvas = new Sprite;
addChild(canvas);
canvas.addChild(bmap);
//Mouse Track Pixel Colors
canvas.addEventListener(MouseEvent.CLICK, readPixel);
}
function readPixel(e:MouseEvent):void
{
var hex:uint = e.bmap.bData.getPixel32(mouseX, mouseY); // <- is the problem?
var pixelAlpha:int = (hex >>> 0x18) & 0xff;
var red:int = (hex >>> 0x10) & 0xff;
var green:int = (hex >>> 0x08) & 0xff;
var blue:int = hex & 0xff;
colorText.text = "Red:" + red + " Green:" + green + " Blue:" + blue + " Alpha:" + pixelAlpha;
}
You are trying to read the field bmap from e who is a MouseEvent and don't have such field.
Also the Bitmap has no field named bData but bitmapData.
One way to get the bitmap from the your sprite is to use the target of the event and use getObjectsUnderPoint to get the bitmap (in case you have multiple bitmap into your sprite)
Also don't forget to take the mouse coordinate from the bmap, otherway you will have to play with Point conversion using globalToLocal and LocalToGlobal
// function to get the bitmap from a display object container
// using the mouse coordinate
function findBitmap(container:DisplayObjectContainer):Bitmap {
if (container === null)
return null;
var childs:Array = container.getObjectsUnderPoint(
new Point(container.mouseX, container.mouseY)
);
while (childs.length > 0) {
var ret:Bitmap = childs.pop() as Bitmap;
if (ret !== null)
return ret;
}
return null;
}
// ....
canvas = new Sprite;
addChild(canvas);
canvas.addChild(bmap);
//Mouse Track Pixel Colors
canvas.addEventListener(MouseEvent.CLICK, readPixel);
// ...
function readPixel(e:MouseEvent):void {
// found the bitmap from the currentTarget
var bmap:Bitmap=findBitmap(e.currentTarget as DisplayObjectContainer);
var hex:uint=0;
if (bmap!==null) {
hex = bmap.bitmapData.getPixel32(bmap.mouseX, bmap.mouseY);
}
var pixelAlpha:int = (hex >>> 0x18) & 0xff;
var red:int = (hex >>> 0x10) & 0xff;
var green:int = (hex >>> 0x08) & 0xff;
var blue:int = hex & 0xff;
colorText.text =
"Red:" + red + " Green:" + green + " Blue:" + blue + " Alpha:" + pixelAlpha;
}
Easiest way is to make your bitmap a property of the canvas so it can easily be referenced from the canvas. The event is firing from the canvas object so e.target will be your canvas. From there, you can hit your bitmap, and the bitmapData property of your bitmap will reference your bitmap data.
function showBitmapData(e:Event):void
{
var bData:BitmapData = new BitmapData(video.width, video.height);
bData.draw(video);
var bmap:Bitmap = new Bitmap(bData);
bmap.x = 220;
bmap.y = 20;
bmap.scaleX = bmap.scaleY = 2;
canvas = new MovieClip(); //sprites can't have arbitrary properites
addChild(canvas);
canvas.bmap = bmap; //*** Look at me! I can be referenced later!
canvas.addChild(bmap);
//Mouse Track Pixel Colors
canvas.addEventListener(MouseEvent.CLICK, readPixel);
}
function readPixel(e:MouseEvent):void
{
var hex:uint = e.target.bmap.bitmapData.getPixel32(mouseX, mouseY); // e.target is your "canvas" from before
var pixelAlpha:int = (hex >>> 0x18) & 0xff;
var red:int = (hex >>> 0x10) & 0xff;
var green:int = (hex >>> 0x08) & 0xff;
var blue:int = hex & 0xff;
colorText.text = "Red:" + red + " Green:" + green + " Blue:" + blue + " Alpha:" + pixelAlpha;
}
The problem is that "e" is an event, which doesn't have a bmap property. It will have a target property, but that will be a event dispatcher, in this case your canvas.
I would suggest:
Create a custom class that extends sprite and contains your bitmap.
Create an instance of that class and add it to the stage.
Add your event listener to that object instead of the stage.
In your event listener check that event.target is an instance of your custom class.
If so, you can use the event's localX and localY to get the pixel value of the object's bitmap property.