Does anyone have a good solution for creating a BitmapData-based scroller in AS3 that can wrap around an image while scrolling to the left and to the right (and also supports arbitrary speed, not just one pixel per loop)? I'm trying to write a fast and lightweight scroller that may only use BitmapData.copyPixels() and BitmapData.scroll().
So far I came up with this code:
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.KeyboardEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.ui.Keyboard;
[SWF(width="800", height="480", frameRate="60", backgroundColor="#000000")]
public class Main extends Sprite
{
private var _source:BitmapData;
private var _sourceWidth:int;
private var _buffer:BitmapData;
private var _canvas:BitmapData;
private var _rect:Rectangle = new Rectangle();
private var _point:Point = new Point();
private var _xOffset:int = 0;
public function Main()
{
_source = new Picture();
_sourceWidth = _source.width;
_rect.width = _source.width;
_rect.height = _source.height;
_canvas = new BitmapData(_source.width, _source.height, false, 0x000000);
_canvas.copyPixels(_source, _rect, _point);
_buffer = _canvas.clone();
var b:Bitmap = new Bitmap(_canvas);
b.x = stage.stageWidth / 2 - _canvas.width / 2;
b.y = 10;
addChild(b);
stage.addEventListener(KeyboardEvent.KEY_DOWN, function(e:KeyboardEvent):void
{
if (e.keyCode == Keyboard.RIGHT) scroll(10);
else if (e.keyCode == Keyboard.LEFT) scroll(-10);
});
}
private function scroll(speed:int):void
{
/* Update offset. */
_xOffset -= speed;
/* Reset rect & point. */
_rect.width = _sourceWidth;
_rect.x = 0;
_point.x = 0;
/* Reached the end of the source image width. Copy full source onto buffer. */
if (_xOffset == (speed > -1 ? -(_sourceWidth + speed) : _sourceWidth - speed))
{
_xOffset = -speed;
_buffer.copyPixels(_source, _rect, _point);
}
/* Scroll the buffer by <speed> pixels. */
_buffer.scroll(-speed, 0);
/* Draw the scroll buffer onto the canvas. */
_canvas.copyPixels(_buffer, _rect, _point);
/* Update rect and point for copying scroll-in part. */
_rect.width = Math.abs(_xOffset);
/* Scrolls to left. */
if (speed > -1)
{
_rect.x = 0;
_point.x = _sourceWidth + _xOffset;
}
/* Scrolls to right. */
else
{
_rect.x = _sourceWidth - _xOffset;
_point.x = 0;
}
trace("rect.x: " + _rect.x + " point.x: " + _point.x);
/* Copy the scrolling-in part from source to the canvas. */
_canvas.copyPixels(_source, _rect, _point);
}
}
}
You can scroll left/right with cursor keys. The scrolling works fine only if the image wraps around once. If deciding to scroll in the opposite direction somewhere in between it will mess up the coordinates to copy the region from the scroll buffer. Basically this block here needs work:
/* Update rect and point for copying scroll-in part. */
_rect.width = Math.abs(_xOffset);
/* Scrolls to left. */
if (speed > -1)
{
_rect.x = 0;
_point.x = _sourceWidth + _xOffset;
}
/* Scrolls to right. */
else
{
_rect.x = _sourceWidth - _xOffset;
_point.x = 0;
}
... The rect.x and point.x need to be set differently here depending on the scroll direction but I don't know how without completely over-complicating the whole loop. Any hints or ideas for a better implementation would be welcome!
This question was asked a while ago, but since it has remained unanswered I'll add my two cents.
I just had to figure this out for a project I'm working on and came up with the following solution. The general idea is to draw the bitmap into the graphics of a display object and to use a matrix transform to control the x and y coords of a viewport over the bitmap data.
public class Example extends Sprite
{
private var vx:Number;
private var vy:Number;
private var coords:Point;
private var shape:Shape;
private var bitmap:Bitmap;
private var bmd:BitmapData;
public function Example()
{
vx = 1.5;
vy = -3.0;
coords = new Point();
shape = new Shape();
bitmap = new Image(); // image, in my case is an embedded image
bmd = bitmap.bitmapData;
addChild(shape);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void
{
coords.x += vx;
coords.y += vy;
// this is important!
// without it, bitmap size constraints are reached and scrolling stops
coords.x %= bitmap.width;
coords.y %= bitmap.height;
shape.graphics.clear();
shape.graphics.beginBitmapFill(bmd, new Matrix(1, 0, 0, 1, coords.x, coords.y), true, true);
shape.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
shape.graphics.endFill();
}
}
Now you can just update the x & y values of the Point object to scroll around the bitmap. You can easily add velocity, acceleration and friction to the equation to make the scrolling more dynamic.
Related
So I'm currently attempting to make a prototype for a Bullet Hell game and I've run into a bit of a dead end.
So far I can move my player perfectly, the boss moves back and forth as he is supposed to, however the projectiles have some funny behaviour. Basically, when the boss moves left/right, so do the projectiles as if they are stuck to him. They move on the y as they are supposed to, except they stop just short of the player and move no further, so I'm hoping anyone can take a look at my code and give me a hand with what's going on.
Note: Ignore the rotation stuff, that's for later implementation, I was just laying the ground work.
Projectile.as
package
{
import flash.display.Stage;
import flash.display.MovieClip;
import flash.events.Event;
public class Projectile extends MovieClip
{
private var stageRef:Stage;
private var _xVel:Number = 0;
private var _yVel:Number = 0;
private var rotationInRadians = 0;
private const SPEED:Number = 10;
public function Projectile(stageRef:Stage, x:Number, y:Number, rotationInDegrees:Number)
{
this.stageRef = stageRef;
this.x = x;
this.y = y;
this.rotation = rotationInDegrees;
this.rotationInRadians = rotationInDegrees * Math.PI / 180;
}
public function update():void
{
this.y += SPEED;;
if(x > stageRef.stageWidth || x < 0 || y > stageRef.stageHeight || y < 0)
{
//this.removeChild(this); <- Causing a crash, will fix later
}
}
}
}
Boss.as
package
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class Boss extends MovieClip
{
private var stageRef:Stage;
private var _vx:Number = 3;
private var _vy:Number = 3;
private var fireTimer:Timer;
private var canFire:Boolean = true;
private var projectile:Projectile;
public var projectileList:Array = [];
public function Boss(stageRef:Stage, X:int, Y:int)
{
this.stageRef = stageRef;
this.x = X;
this.y = Y;
fireTimer = new Timer(300, 1);
fireTimer.addEventListener(TimerEvent.TIMER, fireTimerHandler, false, 0, true);
}
public function update():void
{
this.x += _vx;
if(this.x <= 100 || this.x >= 700)
{
_vx *= -1;
}
fireProjectile();
projectile.update();
}
public function fireProjectile():void
{
if(canFire)
{
projectile = new Projectile(stageRef, this.x / 200 + this._vx, this.y, 90);
addChild(projectile);
canFire = false;
fireTimer.start();
}
}
private function fireTimerHandler(event:TimerEvent) : void
{
canFire = true;
}
}
}
Edit: Current suggestions have been to do the following:
stage.addChild(projectile); and this.parent.addChild(projectile); both which have the projectile firing from the top left corner (0, 0) and not constantly firing from the current center of the Boss.
The other issue, which has been untouched, is the fast that the projectile stops moving after a certain point and remains on the screen.
Another Edit:
After commenting out the code with the timer I have found that the projectile stops moving entirely. The reason why it was stopping after a certain amount of time was due to the timer, when the timer elapsed the projectile stopped and another would fire.
So now I need the projectile to constantly fire and move until it hits the edge of the screen, any ideas?
The problem is you are 'addChild'ing your projectiles to your Boss as opposed the stage (or the same display level as your Boss). When your Boss moves, your projectiles will move relative to him (ie, when he moves sideways, so will they).
When your boss fires a projectile, use a custom event to trigger a fireProjectile method in the Class that is your Boss' display parent. Instantiate your projectiles there and addChild them to the same object to which you addChild your Boss (possibly the stage?).
Alternatively, if you don't want to use a custom event, in your current fireProjectile method change the addChild line to:
this.parent.addChild(projectile);
This will add projectiles to the parent object of your Boss. Although that line seems, slightly, like cheating to me.
I'm new to Flash Actionscript 3.0 and object programming in general. I'm trying to create a simple game, which is drawing a shape based on steering.
public class Player extends Shape
{
public var X,Y,v,vX,vY,size,a,r:Number;
public var k,counter,leftKey,rightKey,_color:uint;
public var line:Shape = new Shape();
public var dot:Shape = new Shape();
/*...*/
/*constructor, giving values to variables here, not important*/
/*...*/
public function Move():void
{
a=a+0.05*k;
//player controls k parameter k=0 by default
//k=1 when right key pressed
//k=-1 when left key pressed
vX=v*Math.cos(a);
vY=v*Math.sin(a);
X=X+vX;
Y=Y+vY;
dot.x=X+vX*size/(2*v);
dot.y=Y+vY*size/(2*v);
if (counter==0)
{
line.graphics.lineTo(X,Y);
if (Math.random()<0.008) counter=12;
} else
{
line.graphics.moveTo(X, Y);
counter--;
}
}
}
Function Move is in my Player class, which is called from inifinite TimerEvent function in my Main Class
public function mainLoop(TimerEvent:Event):void
{
for (var i:uint=0; i<players; i++) player[i].Move();
}
It seems to be working well at the beginning but after some time CPU usage raises dramatically and game becomes unplayble. I belivie it's caused by my shape (line) getting more and more complex.
Is there some reasonable way to optimize it? Can I somehow draw a line in less consuming way? I tried to convert it to bitmap but that looked ugly and didn't really help.
Thanks and cheers!
You're right in assuming that your slowdown in coming from your shape code - vector data is redrawn every frame in flash, so the more complex it is, the longer it takes to draw. Some solutions, depending on what you're willing to do:
Your fidelity is way to high - you're calling your Move function every frame; you probably don't need it that high, as the difference in movement since the last frame is probably less than a pixel. Sample your position every X frames instead (where X is the level of fidelity your willing to go down to). This can be done with a simple counter in the enter frame
If you don't need to keep the entire history of the drawing, put all your points into an array/vector, culling the length as needed. Then every frame, do a line.graphics.clear() and just draw the points in the array
If you do need to keep the entire history, then keep a BitmapData under your line (e.g. the size of the stage). Every so often, draw the line to the BitmapData and call clear() on your graphics to get right of the vector data. You shouldn't notice any loss in quality (set smoothing to true when you're drawing)
I'd do the first point in any case, then choose between the second and third, depending on your use case
Expanding my comment, try something like this:
public class Player extends Shape
{
public var X,Y,v,vX,vY,size,a,r:Number;
public var k,counter,leftKey,rightKey,_color:uint;
public var line:Shape = new Shape();
public var dot:Shape = new Shape();
/*...*/
/*constructor, giving values to variables here, not important*/
/*...*/
public function Player(){
//draw shapes
graphics.lineStyle(1);
graphics.drawCircle(0,0,r);
graphics.lineTo(size,0);//can't test this now, but make sure the line is in the same direction as rotation 0 (guessing it's to the right)
//your other constructor code here
}
public function Move():void
{
a=a+0.05*k;
//player controls k parameter k=0 by default
//k=1 when right key pressed
//k=-1 when left key pressed
vX=v*Math.cos(a);
vY=v*Math.sin(a);
X=X+vX;
Y=Y+vY;
x=X+vX*size/(2*v);
y=Y+vY*size/(2*v);
rotation = a * 57.2957795;//quick'n'dirty radians to degrees
}
and if you want to draw the trails you can try something like this:
var canvas:Bitmap = new BitmapData(state.stageWidth,stage.stageHeight,true,0xFF000000);
var ct:ColorTransform = new ColorTransform(1,1,1,.1);
public function mainLoop(TimerEvent:Event):void
{
for (var i:uint=0; i<players; i++) {
player[i].Move();
canvas.draw(player[i],player[i].transform.concatenatedMatrix,ct);
}
}
Hope this makes sense.
Update
Here is a standalone code snippet to illustrate the idea above(which has untested syntax):
package {
import flash.display.Bitmap;
import flash.geom.ColorTransform;
import flash.display.BitmapData;
import flash.text.TextField;
import flash.ui.Keyboard;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.utils.Dictionary;
import flash.display.Sprite;
public class PlayerMoveTest extends Sprite {
private var keys:Dictionary = new Dictionary();
private var players:Vector.<Player> = new Vector.<Player>();
private var trails:BitmapData;
private var fade:ColorTransform = new ColorTransform(1,1,1,.1);
public function PlayerMoveTest() {
addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
trails = new BitmapData(stage.stageWidth,stage.stageHeight,true,0x00FFFFFF);
addChild(new Bitmap(trails));
for(var i:int = 0 ; i < 2; i++){
var p:Player = addChild(new Player(10+i*10)) as Player;
p.x = stage.stageWidth * .5;
p.y = stage.stageHeight * .5;
players.push(p);
}
stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP,onKeyUp);
stage.addEventListener(Event.ENTER_FRAME,update);
}
private function onKeyDown(e:KeyboardEvent):void{
keys[e.keyCode] = true;
}
private function onKeyUp(e:KeyboardEvent):void{
keys[e.keyCode] = null;
}
private function update(e:Event):void{
if(keys[Keyboard.LEFT] != undefined) {players[0].a -= .05;players[1].a += .05;}
if(keys[Keyboard.RIGHT] != undefined) {players[0].a += .05;players[1].a -= .05;}
if(keys[Keyboard.UP] != undefined) {players[0].s += .15;players[1].s -= .15;}
if(keys[Keyboard.DOWN] != undefined) {players[0].s -= .15;players[0].s += .15;}
for(var i:int = 0 ; i < players.length; i++) {
players[i].move();
trails.draw(players[i],players[i].transform.concatenatedMatrix,fade);
}
}
}
}
import flash.display.*;
class Player extends Shape{
public var vx:Number,vy:Number,a:Number,size:Number,r:Number,s:Number;
public function Player(size:Number){
init(size);
}
private function init(size:Number):void{
vx = vy = a = s = 0;
this.size = size;
this.r = size * .25;
graphics.lineStyle(1);
graphics.drawCircle(0,0,r);
graphics.lineTo(size,0);
}
public function move():void{
rotation = a * 57.2957795;
vx = Math.cos(a) * s;
vy = Math.sin(a) * s;
x += vx;
y += vy;
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x > stage.stageWidth) x = stage.stageWidth-width;
if(y > stage.stageHeight) y = stage.stageHeight-height;
}
}
You can test this code here and here's a preview:
Use the arrow keys to drive(up arrow accelerates, left/right steer).
The first player is the smaller one, having the correct controls, the other is simply mirroring the previous controls)
I've edited the following code in order to let those green rectangles to follow my cursor which is customized by a small rectangle. But I've encountered several problems:
Although I haven't defined any coordinate in the separate class, but the size is abviously wrong in the stage when publish with only half size for the cursor coordinate.
The reset button cannot be activated, although I've tested well in the other code.
Here is the work I've published: http://neowudesign.com/hwu_ex04.html
The code on timeline
//hw//Creating a new cursor
newcursor.startDrag ("true");
Mouse.hide();
//hw//Creating a holder to hold the butterfly objects
var mothHolder = new Sprite();
addChild(mothHolder);
//hw//Creating seven moths at the beginning
makeMoths(7);
//hw//creating a function which can generate limited numbers of moths.
function makeMoths(MothsNumber:Number)
{
for (var i = 0; i < MothsNumber; i++)
{
newMoth = new Moth();
mothHolder.addChild(newMoth);
}
}
//hw//Set the reset button at the top for clicking, but it's failed to work;
//hw//Set the cursor back to the default one, and remove the custom one when hovering;
mothHolder.setChildIndex(reset,mothHolder.numChildren);
reset.addEventListener(MouseEvent.MOUSE_OVER, cursorchange);
function cursorchange(event:MouseEvent):void
{
Mouse.show();
newcursor.visible = false;
trace("alert!!");
}
//hw//creating a function of reset
reset.addEventListener(MouseEvent.CLICK, resetClick, false, 0, true);
function resetClick(evt:MouseEvent):void
{
removeChild(mothHolder);
mothHolder = new MovieClip();
addChild(mothHolder);
var numMoths:Number = Math.round(Math.random() * 6) + 1;
trace("Moths Numeber: "+ numMoths);
makeButterflies(numButterflies);
}
//hw//when the cursor leave the reset region, it turns back to the customized one
reset.addEventListener(MouseEvent.MOUSE_OUT, fl_MouseOutHandler);
function fl_MouseOutHandler(event:MouseEvent):void
{
removeEventListener(MouseEvent.MOUSE_OVER, cursorchange);
Mouse.hide();
newcursor.visible = true;
}
And the code for class "Moth" separately named "angle.as"
package {
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.Point;
public class angle extends MovieClip {
var speed:Number = 8;
function angle() {
//letting every moth follow the moving of the cursor
addEventListener(Event.ENTER_FRAME,mothMove);
function mothMove(myEvent:Event) {
trace(mouseX);
trace(mouseY);
var angle:Number = Math.atan2(mouseY - y, mouseX - x);
x += Math.cos( angle ) * speed;
y += Math.sin( angle ) * speed;
}
}
}
}
I'm looking to check if a BitmapData object is completely obscured by a BitmapData object. Is there something like the hitTest function but that makes sure every pixel is covered instead of any pixel?
Edit: It is important that transparent pixels are not included when checking if the object is obscured.
It's actually a pretty simple solution after all! Basically what you do is just capture the pixel values of the overlapping bitmap in the rectangle area that the overlapped bitmap occupies. You then iterate over that vector of values and as long as you don't have a 0 (completely transparent pixel), then you've completely covered the bitmap underneath.
Here are the two bitmaps I used in this test:
Overlapping bitmap:
Overlapped bitmap:
Code:
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.utils.ByteArray;
import flash.geom.Rectangle;
var coveredBitmapData:BitmapData = new CoveredBitmapData();
var coveringBitmapData:BitmapData = new CoveringBitmapData();
var coveringBitmap:Bitmap = new Bitmap(coveringBitmapData, "auto", true);
var coveredBitmap:Bitmap = new Bitmap(coveredBitmapData, "auto", true);
coveredBitmap.x = Math.random() * (stage.stageWidth - coveredBitmap.width);
coveredBitmap.y = Math.random() * (stage.stageHeight - coveredBitmap.height);
stage.addChild(coveredBitmap);
stage.addChild(coveringBitmap);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMovement);
function onMouseMovement(e:MouseEvent):void
{
coveringBitmap.x = mouseX - (coveringBitmap.width * .5);
coveringBitmap.y = mouseY - (coveringBitmap.height * .5);
checkIfCovering(coveringBitmap, coveredBitmap);
}
function checkIfCovering(bitmapA:Bitmap, bitmapB:Bitmap):Boolean
{
//bitmapA is the covering bitmap, bitmapB is the bitmap being overlapped
var overlappedBitmapOrigin:Point = new Point(bitmapB.x, bitmapB.y);
var localOverlappedBitmapOrigin:Point = bitmapA.globalToLocal(overlappedBitmapOrigin);
var overlappingPixels:Vector.<uint> = bitmapA.bitmapData.getVector(new Rectangle(localOverlappedBitmapOrigin.x, localOverlappedBitmapOrigin.y, bitmapB.width, bitmapB.height));
if(overlappingPixels.length == 0) {
//This means that there is no bitmap data in the rectangle we tried to capture. So we are not at all covering the underlying bitmap.
return false;
}
var i:uint = 0;
for(i; i < overlappingPixels.length; ++i) {
if(overlappingPixels[i] == 0) {
return false;
}
}
return true;
}
So you want to see if object2 completely covers object1 (both Bitmaps)?
var left:Boolean = object2.x <= object1.x;
var top:Boolean = object2.y <= object1.y;
var right:Boolean = object2.x + object2.width >= object1.x + object1.width;
var bottom:Boolean = object2.y + object2.height >= object1.y + object1.height;
if(left && right && top && bottom)
{
// Completely covered.
}
It's my first time with Papervision3D and I have created a slide show of images that is skewed on the y-axis. Smallest photos on the left, and they increase in size going to the right. So they zoom from left to right, smallest to biggest.
I have a tooltip that pops up when you hovers over the photo, but the tooltip also gets skewed proportionate to the camera view (slanted). I want the tooltip's angle to be independent of the entire camera view.
Any idea how to rotate objects independent of the parent's camera angle?
Thanks!
my_obj = new DisplayObject3D();
my_plane = my_obj.addChild(new Plane(bla bla));
my_obj.lookAt(camera);
The 'lookAt' bit is what you need.
Why not draw the tooltips in 2d? You can get the on-screen position of the images and then just draw a regular Sprite like so:
package
{
import flash.display.Sprite;
import flash.text.TextField;
import org.papervision3d.events.InteractiveScene3DEvent;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Plane;
import org.papervision3d.typography.Text3D;
import org.papervision3d.view.BasicView;
public class PVTest extends Sprite
{
private var world:BasicView;
private var text:Text3D;
private var text2d:TextField;
public function PVTest()
{
world = new BasicView(stage.width, stage.height, true, true);
var colorMat:ColorMaterial = new ColorMaterial();
colorMat.interactive = true;
var planeContainer:DisplayObject3D = new DisplayObject3D();
var plane:Plane;
for(var i:int = 0; i < 11; i++)
{
plane= new Plane(
colorMat,
100, 100,
10);
plane.x = (i * 105) - 500;
plane.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, handleMouseOver);
plane.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, handleMouseOut);
planeContainer.addChild(plane);
}
planeContainer.rotationY = 10;
world.scene.addChild(planeContainer);
world.camera.z = -500;
addChild(world);
world.startRendering();
}
private function handleMouseOver(event:InteractiveScene3DEvent):void
{
var plane:Plane = Plane(event.displayObject3D);
plane.calculateScreenCoords(world.camera);
const OFFSET_X:int = -20;
const OFFSET_Y:int = 30;
text2d = new TextField();
text2d.text = "toolTip";
text2d.x = plane.screen.x + (stage.width/2) + OFFSET_X;
text2d.y = plane.screen.y + (stage.height/2) + OFFSET_Y;
addChild(text2d);
}
private function handleMouseOut(event:InteractiveScene3DEvent):void
{
removeChild(text2d);
}
}
}
Even for this example you'd have to offset the y position of the tooltip based on the objects scale but it may be easier than working out the rotations and is the best way to get a consistent looking result.