I can get screens resolution when my application starts, but can I know when the user has changed the resolution?
Something like a event (example):
Screen.mainScreen.addEventListener("CHANGE_RESOLUTION", ...)
I've tried using a setInterval to monitor the resolution, but is this the best way to do this?
var resolution:Rectangle = Screen.mainScreen.bounds;
setInterval(function():void {
if(!Screen.mainScreen.bounds.equals(resolution)) {
trace("changed!");
}
}, 1000);
As far as I know, polling (what you're currently doing) is the only way in AS3 to detect screen resolution changes.
However, if you are in a maximized or fullscreen window, the window (or stage if set to NO_SCALE) will fire a resize event on a resolution change. (see the answer from Diniden). Though I'd recommend listening on the stage.nativeWindow object instead of stage itself.
I'd say that you are doing it a perfectly acceptable way currently.
Keep in mind though, that if this is a desktop application, the user could have your program on a monitor that is not the primary one (Screen.mainScreen). To support that scenario, you'd want to do something like the following:
//a constant to use for a custom event
const RESOLUTION_CHANGE:String = "resolution_change";
var resolution:Rectangle = curScreen.bounds;
//Adobe recommends using timers instead of setInterval - using ENTER_FRAME may be too expensive for your needs, but would afford the quickest reaction time.
var resolutionTimer:Timer = new Timer(1000); //poll every second (adjust to optimum performance for your application)
resolutionTimer.addEventListener(TimerEvent.TIMER, pollScreenResolution);
resolutionTimer.start();
//get the screen that the window is on
function get curScreen():Screen {
//get the first screen [0] that the current window is on
return Screen.getScreensForRectangle(stage.nativeWindow.bounds)[0];
}
function pollScreenResolution(e:TimerEvent) {
if(!curScreen.bounds.equals(resolution)) {
trace("changed!");
//dispatch our custom event
stage.dispatchEvent(new Event(RESOLUTION_CHANGE));
resolution = curScreen.bounds; //set the resolution to the new resolution so the event only dispatches once.
}
}
Now you can listen for that event in other parts of your application.
stage.addEventListener(MovieClip(root).RESOLUTION_CHANGE, doSomething);
This is how I handle window resize events. You look at the stage of the given window for seeing how it's being resized. The other properties is to make it resize in a sensible way.
this.stage.scaleMode = StageScaleMode.NO_SCALE;
this.stage.align = StageAlign.TOP_LEFT;
stage.addEventListener(Event.RESIZE, stageResized);
Related
Under certain conditions, picking a resolution with Camera.setMode() adds black bars to the camera input, "letterboxing" it. I understand that setMode() uses some kind of hidden algorithm that picks a resolution from one of your camera's available resolutions and then crops it to fit your desired dimensions, but apparently sometimes it would rather add black bars than crop it.
This behavior is dependent on what camera I'm using. Some cameras seem to always crop and never letterbox. This may be related to what available resolutions they have. But what's really strange is that the letterboxing only ever happens when I try it in a Flash Player ActiveX control, like in Internet Explorer. It doesn't happen when I try the exact same SWF in Flash Player Projector or Google Chrome. This seems to imply that different Flash Player versions use a different algorithm to select and fit a resolution to the desired dimensions.
Here's a very simple example of code that's been creating this problem for me. In this case I'm providing a 4x3 resolution to setMode(), which means it must be selecting a 16x9 resolution even though 640x480 is one of the camera's available resolutions.
public class Flashcam extends Sprite
{
private var _camera:Camera = Camera.getCamera("0");
public var _video:Video;
private var _width:int = 640;
private var _height:int = 480;
public function Flashcam()
{
_camera.setMode(_width, _height, 15);
_video = new Video(_camera.width, _camera.height);
addChild(_video);
_video.attachCamera(_camera);
}
}
Is there any way to stop Camera from letterboxing its input? If not, is there some way to tell whether or not it's being letterboxed and which camera resolution has been automatically selected so that I can write my own code to account for it?
Windows computer running AIR.
Every night the educational displays get turned off. The computers stay on.
On certain displays when they turn them on in the morning the screen resolution goes back and forth a few times starting at 1920 x 1080 then to 1024 x 768 then back to 1920 x 1080.
When this happens for some reason the AIR app freaks out and stays at 1024 x 768 which doesn't take up the fullscreen and you can see the desktop. We have to manually relaunch the AIR app.
Is there a way when this happens we can detect and go back to force Fullscreen?
Thanks in advance for any suggestions.
If you are using a maximized window, you can listen for Event.RESIZE on the stage (dispatched when the window get's resized), and or listen for the native windows displayStateChange or resize events.
If you are using the FULL_SCREEN (or FULL_SCREEN_INTERACTIVE) display state, you can listen for the FullScreenEvent.FULL_SCREEN event to know when that has changed.
Here is an example of a few things you can try:
//in your document class or main timeline, listen for the following events:
stage.nativeWindow.addEventListener(NativeWindowBoundsEvent.RESIZE, windowResized);
//the above will fire anytime the window size changes, Really this is all you need as this event will fire when the window display state changes as well.
stage.nativeWindow.addEventListener(NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGE, windowResized);
//the above will fire anytime the window state changes - eg. Maximized/Restore/Minimize. This likely won't trigger on a resolution change, but I've included it anyway
stage.addEventListener(FullScreenEvent.FULL_SCREEN, fullscreenChange);
//the above will fire whenever you enter or leave fullscreen mode (stage.displayState)
private function windowResized(e:Event):void {
//re-maximize the window
stage.nativeWindow.maximize();
//OR Go to full screen mode
stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
}
private function fullscreenChange(e:FullScreenEvent):void {
if (!e.fullScreen) {
//in half a second, go back to full screen
flash.utils.setTimeout(goFullScreen, 500);
}
}
private function goFullScreen():void {
stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
}
I'm building a mixed interaction AS3/AIR application. Some of my scenes use the regular CPU rendered display classes (flash.display.Sprite, flash.display.MovieClip) and another high-density graphics mode uses Starling. I can switch between and use these happily, and I can switch between windowed mode at 720p to fullscreen mode just fine.
However, I just added a StageWebView to easily add some HTML based content, and I'm having trouble with resize. If I don't call "new Starling()" then the StageWebView instance will correctly resize to maintain the same relative screen dimensions when I switch from windowed to FullScreen. However, as soon as I create a Starling instance, the StageWebView will not correctly resize on fullscreen; it retains the absolute dimensions originally specified. If I don't instantiate Starling, the StageWebView resizes perfectly.
Do I need an event handler for Event.RESIZE to specifically adjust the viewPort member of the StageWebView object? How do I get the correct relative dimensions from the original fixed windowed size? (I tried looking at stage width/height and the numbers were oddly large, with AIR failing to build a new Rectangle correctly).
I would appreciate any sample code for anyone who has solved this problem. Thanks.
Okay, fixed this. While without Starling, the regular Flash Renderer will handle resizing the StageWebView class correctly, with Starling, we must handle our own resize events.
Thus, in the onAddedToStage method (which is itself a handler for Event.ADDED_TO_STAGE) of my Sprite-derived container class, I add this:
addEventListener(Event.RESIZE, onResize);
And then have this function:
private function onResize(e:Event):void
{
var origH:Number = 720;
var origW:Number = 1280;
var baseRect:Rectangle = new Rectangle(40,40, 1200, 640);
var newX:Number = baseRect.x * stage.stageWidth / origW;
var newY:Number = baseRect.y * stage.stageHeight / origH;
var newW:Number = baseRect.width * stage.stageWidth / origW;
var newH:Number = baseRect.height * stage.stageHeight / origH;
m_webView.viewPort = new Rectangle(newX,newY,newW,newH);
}
Of course, I shouldn't hardcode the 720 and 1280, nor the base size of the viewPort, but you get the idea.
I created a web game with EaselJS, which have a few (2-6) Sprites that are moving on the canvas. Also they may scale up and down according to mouse events.
Here is how I move these Sprites:
container.enemies.forEach(function(drop) {
drop.x += drop.vx;
drop.y += drop.vy;
checkHitWall(drop);
var collided = drop.checkCollision(container.enemies);
if (collided) {
distributeVelocity(drop, collided);
}
});
Here is how I change their size:
growingObject = function(obj) {
if (obj.radius > canvasWidth / 4) {
return;
}
var rate = 1.01;
obj.gotoAndStop("growing");
obj.radius *= rate;
obj.scaleX = obj.scaleY = obj.scale * rate;
obj.scale = obj.scaleX;
}
and this function is called every tick when mouse is down.
The background is cached when it is created.
I don't know if there's best practice for moving/growing sprites with EaselJS.
The game runs find on a desktop browser, but is very laggy on a android phone.
I think these Sprites are not a heavy load for a phone. The profiling result shows draw method consumes most CPU time. How could I optimize this?
One idea: You can try reducing your framerate to reach a balance between smooth animation and acceptable speed. In my experience you can go to surprisingly low framerates on mobile before the game starts looking unacceptable. Try something like this, and adjust your velocity to simulate the same kind of speed you had at your previous fps. Obviously the exact tweaking is up to you.
createjs.Ticker.setFPS(12);
I'm currently trying to speed my web app for mobile devices a little bit up, but now I'm stuck at the most important part - caching. How is it possible to cache a entire layer right before the user starts to drag it and revert it back to usable Kinetic.Nodes when the drag-action has stopped?
At the moment I start caching on
stage.on('mousedown touchstart', function(){ // CACHING})
but the problem here is, that the user has to perform a second mousedown touchstart event to "grab" the cached image, which, of course, starts a new caching.
In this case my questions would be:
How can I pass the mousedown touchstart event to the cached image,
so the user can drag it with one fluent movement?
How can I speed up caching? (It takes 1-2 seconds for the cached image to appear. Is it useful to cache it in a setInterval after every, lets say 4 secs, and use this precached image or causes that a too high performance drain?)
I highly appreciate any kind of suggestions regarding my problem or further tips&tricks to speed up things.
Based on this statement:
stage.on('mousedown touchstart', function(){ // CACHING})
I'm assuming that on mousedown touchstart you call layer.toImage() or stage.toImage() and you want to drag the new image on that one click/tap.
You can invoke the drag event on the new generated image by using the .startDrag() function: Kinetic.Shape#startDrag
You can then invoke .stopDrag() on mouseup touchend to stop the drag. Kinetic.Shape#stopDrag
Something like:
var image, ox, oy;
stage.on('mousedown touchstart', function(){
// CACHING
stage.toImage({
width: stage.getWidth(),
height: stage.getHeight(),
callback: function(img) {
var image = new Kinetic.Image({
image: img,
draggable: true
});
ox = image.getX();
oy = image.getY();
image.startDrag();
}
});
});
stage.on('mouseup touchend', function(){
image.stopDrag();
//Calculate dx, dy to update nodes.
var newX = image.getX();
var newY = image.getY();
var dx = newX-ox;
var dy = newY-oy;
var children = layer.getChildren();
for (var i=0; i<children.length; i++) {
children.setX(children.getX()+dx);
children.setY(children.getY()+dy);
}
image.hide(); //or remove() or destroy()
layer.draw();
});
Note you need to update your original nodes after dragging the cached layer.
Another Note I haven't tested the code but I believe you could do something along the lines of what I've got up there.
Small UPDATE: You also should probably hide() the original layer while dragging the cached layer image! :) And then show() it again when you hide the cached image layer.
Honestly I'm not sure how you would speed up that cache time unless you can predict when the user needs to click/tap the stage to move. I think your suggestion would cost more performance than it would save.
I'm guessing that the desktop caches the image faster than on your mobile? It might fall into just being a limitation of KineticJS performance on Mobile vs Desktop...
UPDATE
Okay, I have an idea for #2, it's not exactly a fix but it might work better for you.
Separate your stage mousedown event with your touchstart event. mousedown will be the same but touchstart we want to handle differently.
On stage touchstart we want to drag the entire stage like normal, but meanwhile run the code for the original mousedown to cache the layer.
When the cached image finishes loading (takes 1-2 seconds you say), use .stopDrag() on the original stage and hide it. At this moment you want to store the current x and y values of the stage, so that you can still calculate dx,dy. Then immediately call .startDrag() on the new cached image and continue on like we did for mousedown.
How to know when the cached image finishes loading? I think that's what the toImage() callback function is for. If not, than hopefully a javascript onload event will work to determine when the image finishes generating.
The end result will be that you'll get your slow choppy drag on the stage for touch events UNTIL the image is cached. Then we flip the switch and stop dragging the stage, start dragging the cached image, and then revert/update the stage on touchend.
Hope that works as a semi-solution to your problem...
ANOTHER UPDATE
Okay here's another idea that actually might help your performance!
If your stage isn't modifying nodes very often, you can pre-cache the stage image so that it's already generated, and .hide() it. Then when you need to drag it, you just need to set the x,y of the cached image to match the stage's current x,y and then .show() the cached image. This will eliminate the time needed to wait/load the cached image when you first start dragging.
If you do happen to add a node or move a node or anything, after that cache the image. Hopefully this is manageable as we don't want to cache the image too often (drains performance). Again the cached image will be ready for your stage.drag event beforehand.
The goal is to have the stage cached before you do mousedown touchstart and start dragging. Hopefully this helps.