/** * Copyright 2008 - 2015 The Loon Game Engine Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loon * @author cping * @email:javachenpeng@yahoo.com * @version 0.5 */ package loon.html5.gwt; import loon.Accelerometer; import loon.Asyn; import loon.LGame; import loon.LSetting; import loon.Log; import loon.Save; import loon.Support; import loon.event.InputMake; import loon.html5.gwt.preloader.LocalAssetResources; import loon.jni.NativeSupport; import loon.jni.TimerCallback; import loon.utils.reply.Act; import com.google.gwt.animation.client.AnimationScheduler; import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; import com.google.gwt.core.client.Duration; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Cursor; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Panel; public class GWTGame extends LGame { private static final int MIN_DELAY = 5; /** * 由于手机版的浏览器对webgl支持实在各种奇葩,不同手机环境差异实在惊人,干脆把常用的刷新方式都写出来,用户自己选…… */ public static enum Repaint { // RequestAnimationFrame效率最高 // Schedule在某些情况下更适用(有间断时) // AnimationScheduler本质是前两者的api混合,会等待canvas渲染后刷新,虽然效率最低,但是最稳,不容易造成webgl卡死现象 // 为了稳定考虑,所以默认用这个. RequestAnimationFrame, Schedule, AnimationScheduler; } public static class GWTSetting extends LSetting { // 经过几天来的实测,webgl对不同浏览器(以及在不同手机环境)下的差异太大,于是把刷新模式也交给用户定制好了…… // 暂时来说,canvas还是目前手机版html5的王道,webgl差异不解决,很难推(大家都用chrome世界就完美了……) /** * 经过几天的反复测试,最终还是默认用gwt提供的AnimationScheduler刷新(本质还是RequestAnimationFrame * 但是不同平台上综合来说,这个有对象绑定,会匀速刷新canvas,不容易造成卡死……) **/ public Repaint repaint = Repaint.AnimationScheduler; // 是否支持使用flash加载资源(如果要做成静态文件包,涉及跨域问题(也就是非服务器端运行时),所以需要禁止此项) public boolean preferFlash = false; // 当前浏览器的渲染模式 public Mode mode = GWTUrl.Renderer.requestedMode(); // 当此项存在时,会尝试加载内部资源 public LocalAssetResources internalRes = null; // 当此项存在时,同样会尝试加载内部资源 public boolean jsloadRes = false; public boolean transparentCanvas = false; public boolean antiAliasing = true; public boolean stencil = false; public boolean premultipliedAlpha = false; public boolean preserveDrawingBuffer = false; // 如果此项开启,按照屏幕大小等比缩放 public boolean useRatioScaleFactor = false; // 需要绑定的层id public String rootId = "loon-root"; // 初始化时的进度条样式(不实现则默认加载) public GWTProgress progress = null; // 如果此项为true,则仅以异步加载资源 public boolean asynResource = false; } public static enum Mode { WEBGL, CANVAS, AUTODETECT; } public static class AgentInfo extends JavaScriptObject { public final native boolean isFirefox() /*-{ return this.isFirefox; }-*/; public final native boolean isChrome() /*-{ return this.isChrome; }-*/; public final native boolean isSafari() /*-{ return this.isSafari; }-*/; public final native boolean isOpera() /*-{ return this.isOpera; }-*/; public final native boolean isIE() /*-{ return this.isIE; }-*/; public final native boolean isMacOS() /*-{ return this.isMacOS; }-*/; public final native boolean isLinux() /*-{ return this.isLinux; }-*/; public final native boolean isWindows() /*-{ return this.isWindows; }-*/; protected AgentInfo() { } } public void setTitle(String title) { Window.setTitle(title); } public void setCursor(Cursor cursor) { Element rootElement = graphics.rootElement; if (cursor == null) { rootElement.getStyle().setProperty("cursor", "none"); } else { rootElement.getStyle().setCursor(cursor); } } public void disableRightClickContextMenu() { disableRightClickImpl(graphics.rootElement); } private final static Support support = new NativeSupport(); static final AgentInfo agentInfo = computeAgentInfo(); final GWTSetting gwtconfig; private final double start = initNow(); public Act<LGame> frame = Act.create(); private final GWTLog log = GWT.create(GWTLog.class); private final Asyn syn = new Asyn.Default(log, frame); private final GWTAccelerometer accelerometer = new GWTAccelerometer(); private final GWTAssets assets; private final GWTGraphics graphics; private final GWTInputMake input; private final GWTSave save; private final Loon game; public GWTGame(Loon game, Panel panel, GWTSetting config) { super(config, game); GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() { @Override public void onUncaughtException(Throwable e) { reportError("Uncaught Exception: ", e); } }); this.game = game; this.gwtconfig = config; log.info("Browser orientation: " + game.getOrientation()); log.info("Browser screen width: " + game.getContainerWidth() + ", screen height: " + game.getContainerHeight()); log.info("devicePixelRatio: " + Loon.devicePixelRatio() + " backingStorePixelRatio: " + Loon.backingStorePixelRatio()); if (config.useRatioScaleFactor) { int width = setting.width; int height = setting.height; float scale = Loon.devicePixelRatio(); width *= scale; height *= scale; setting.width_zoom = width; setting.height_zoom = height; setting.updateScale(); // 若缩放值为无法实现的数值,则默认操作 } try { graphics = new GWTGraphics(panel, this, config); input = new GWTInputMake(this, graphics.rootElement); assets = new GWTAssets(this); save = new GWTSave(this); } catch (Throwable e) { log.error("init()", e); Window.alert("failed to init(): " + e.getMessage()); throw new RuntimeException(e); } if (setting != null && setting.appName != null) { setTitle(setting.appName); } initProcess(); } private boolean initGwt = false; private void init() { if (!initGwt) { if (game != null && game.isMobile()) { Window.scrollTo(0, 1); } game.initialize(); initGwt = true; } } public void start() { Repaint repaint = game.config.repaint; // 此处使用了三种不同的画面刷新模式,万一有浏览器刷不动,大家可以换模式看看…… switch (repaint) { case RequestAnimationFrame: requestAnimationFrame(game.setting.fps, new TimerCallback() { @Override public void fire() { init(); requestAnimationFrame(game.setting.fps, this); emitFrame(); } }); break; case AnimationScheduler: AnimationScheduler.get().requestAnimationFrame( new AnimationCallback() { @Override public void execute(double timestamp) { init(); emitFrame(); AnimationScheduler.get().requestAnimationFrame( this, graphics.canvas); } }, graphics.canvas); break; case Schedule: final int framed = (int) ((1f / game.setting.fps) * 1000f); new Timer() { @Override public void run() { init(); Duration duration = new Duration(); emitFrame(); this.schedule(Math.max(MIN_DELAY, framed - duration.elapsedMillis())); } }.schedule(framed); break; } } @Override public Type type() { return Type.HTML5; } @Override public double time() { return now(); } @Override public int tick() { return (int) (now() - start); } @Override public void openURL(String url) { Window.open(url, "_blank", ""); } @Override public GWTAssets assets() { return assets; } @Override public GWTGraphics graphics() { return graphics; } @Override public Asyn asyn() { return syn; } @Override public InputMake input() { return input; } @Override public Log log() { return log; } @Override public Save save() { return save; } @Override public Support support() { return support; } @Override public Accelerometer accel() { return accelerometer; } private native JavaScriptObject getWindow() /*-{ return $wnd; }-*/; private native void requestAnimationFrame(float frameRate, TimerCallback callback) /*-{ var fn = function() { callback.@loon.jni.TimerCallback::fire()(); }; if (frameRate < 60) { $wnd.setTimeout(fn, 1000 / frameRate); } else { if ($wnd.requestAnimationFrame) { $wnd.requestAnimationFrame(fn); } else if ($wnd.mozRequestAnimationFrame) { $wnd.mozRequestAnimationFrame(fn); } else if ($wnd.webkitRequestAnimationFrame) { $wnd.webkitRequestAnimationFrame(fn); } else if ($wnd.oRequestAnimationFrame) { $wnd.oRequestAnimationFrame(fn); } else if ($wnd.msRequestAnimationFrame) { $wnd.msRequestAnimationFrame(fn); } else { $wnd.setTimeout(fn, 16); } } }-*/; private static native AgentInfo computeAgentInfo() /*-{ var userAgent = navigator.userAgent.toLowerCase(); return { isFirefox : userAgent.indexOf("firefox") != -1, isChrome : userAgent.indexOf("chrome") != -1, isSafari : $wnd.opera || userAgent.indexOf("safari") != -1, isOpera : userAgent.indexOf("opera") != -1, isIE : userAgent.indexOf("msie") != -1 || userAgent.indexOf("trident") != -1, isMacOS : userAgent.indexOf("mac") != -1, isLinux : userAgent.indexOf("linux") != -1, isWindows : userAgent.indexOf("win") != -1 }; }-*/; private static native void disableRightClickImpl(JavaScriptObject target) /*-{ target.oncontextmenu = function() { return false; }; }-*/; private static native double initNow() /*-{ if (!Date.now) Date.now = function now() { return +(new Date); }; return Date.now(); }-*/; private static native double now() /*-{ return Date.now(); }-*/; /** * 检测浏览器窗体是否被隐藏起来 * * @return */ private native boolean isHidden() /*-{ return $doc.hidden; }-*/; public boolean isShow() { return isHidden(); } @Override public boolean isMobile() { if (game == null) { return false; } return super.isMobile() || game.isMobile(); } }