更新:ファイナルイベントは、参加いただく皆さまと関係者の安全を最優先として検討を重ねた結果、一般公開での開催は見送ることといたしました。これにともなう審査員と賞品についての変更は後日改めてご案内します。今後、一般公開のイベントはございませんが、審査員が、トップ 20 作品に選出されたデベロッパーの皆さまによるプレゼンテーション、質疑応答や試遊などを通し、厳正なる審査を行い、結果を発表します。ご案内までもうしばらくお待ち下さい。
●Android Studio プロファイラ : Android Studio の System Trace プロファイラを全面的に見直し、コードがどのように実行されているのかをデベロッパーが詳しく調査して視覚化できるようにしました。さらに、ゲームがどのようにメモリを割り当てているのかを確認したり、メモリリークを見つけたりできるように、ネイティブメモリプロファイリング機能も追加しました。Android Studio 4.1 Canary をダウンロードし、セッションもご覧ください。 ●Visual Studio 用の Android ゲーム開発拡張機能 : クロスプラットフォームゲームに Android サポートを簡単に追加できるようにする新ツールを導入します。これは、既存の Visual Studio ベースのワークフローに簡単に組み込めるので、APK の生成、Android 端末やエミュレータへのデプロイ、Visual Studio からの Android ゲームのデバッグを効率よく行えるようになります。デベロッパー プレビューに申し込み、セッションもご覧ください。 ●Android GPU Inspector : 新しい Android GPU Inspector を使うと、Android GPU を詳細に分析し、ゲームのレンダリングステージや GPU カウンターについての詳しい情報を確認できます。これにより、グラフィック エンジニアは情報や分析を活用し、ゲームを最適化してフレームレートや電池寿命を向上できるようになります。デベロッパー プレビューに申し込み、セッションもご覧ください。 ●Google の Unity 用ゲームパッケージレジストリ : この新しいパッケージレジストリは、さまざまな Google API を統合します。Google Play Billing、Android App Bundle、Play Asset Delivery、Play Instant、Firebase for Games を始めとして、あらゆるものが 1 つにまとまっています。詳細を確認し、セッションもご覧ください。 ●Crytek が Android のサポートを発表 : CRYENGINE は、PC やゲーム機向けの高パフォーマンス ゲームエンジンとして知られています。この夏、このエンジンに完全な Android パイプラインが加わります。詳細はこちらをご覧ください。
●Google Play Asset Delivery : App Bundle インフラストラクチャをベースに構築された、ゲームサービスを配信するための一連の新機能で、適切なゲームアセットを適切なタイミングで適切な端末に直接無償で配信します。この機能により、プレイヤーはアセットのダウンロード中にこれまでよりも早くゲームを楽しめることができ、デベロッパーはゲームのリソースのホスティングや配信にかかる費用を節約できます。詳細を確認し、セッションもご覧ください。 ●Android vitals ネイティブクラッシュシンボリケーション : Play Console でシンボリケーションがサポートされ、ネイティブ コードでのクラッシュをさらに簡単にデバッグできるようになります。この機能は、ネイティブのデバッグシンボルをアップロードするだけで利用できます。オープンベータ版に申し込み、セッションもご覧ください。 ●Android vitals パフォーマンス インサイトと Android Performance Tuner : Android vitals の新しいパフォーマンス インサイトを活用し、フレームレートや多くの端末での再現度を最適化しましょう。新しいインサイトを得るには、Android Game SDK の新ライブラリである Android Performance Tuner をゲームに組み込みます。デベロッパー プレビューに申し込み、セッションもご覧ください。 ●Unity デベロッパー用 Play Billing Library 2 : Unity を使っているゲーム デベロッパーが、Play Billing Library 2 の全機能にアクセスできるようになり、現金払いやゲーム外での IAP 購入なども利用できます。これは、Unity デベロッパーが 2021 年の Play の Billing Library バージョン要件に対応する最善の方法です。詳細はこちらをご覧ください。
●品質を強調 : 優れた技術的パフォーマンスを備え、クラッシュしない、没入できるゲームプレイを推進するため、Google Play 全体で高品質なゲーム エクスペリエンスを強調する活動を続けています。詳細はこちらをご覧ください。 ●事前登録 : 毎年、数億人のプレイヤーに利用されている Google Play の事前登録キャンペーンは、リリース時の到達範囲を効率的に拡大する方法です。早い時点でユーザーに気づいてもらい、リリース前の需要を逃さないように、事前登録されたすべてのゲームをローンチ初日に自動インストールする機能を近日中にロールアウトする予定です。 ●Play Pass : 昨年後半に米国市場でサービスを開始した Play Pass は、ユーザーが Google Play で数 100 個のすばらしいアプリやゲームが利用できるサブスクリプション サービスです。広告やアプリ内購入はありません。詳細を確認し、お申し込みください。
<amp-video src="video.mp4" width="300" height="200"></amp-video>
<script async custom-element="amp-video" src="https://cdn.ampproject.org/v0/amp-video-0.1.js"></script>
<div amp-fx="fade-in">I will fade in</div>
<script async custom-element="amp-fx-collection" src="https://cdn.ampproject.org/v0/amp-fx-collection-0.1.js"></script>
<html> <head> <title>My Page</title> <link rel="canonical" href="/mypage.html" /> </head> <body> <h1>Hello World!</h1> </body>
<!doctype html> <html > <head> <meta charset="utf-8"> <title>My Page</title> <link rel="canonical" href="/mypage.html" /> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <style amp-boilerplate>...</noscript> <script async src="https://cdn.ampproject.org/v0.js"></script> </head> <body> <h1>Hello World!</h1> </body> </html>
export const config = {amp: true}; export default () => ( <div amp-fx='fade-in'>I will fade-in</div> );
import Head from 'next/head' export const config = {amp: true}; export default () => ( <> <Head> <script async key="amp-fx-collection" custom-element="amp-fx-collection" src="https://cdn.ampproject.org/v0/amp-fx-collection-0.1.js" /> </Head> <div amp-fx='fade-in'>I will fade-in</div> </> );
README.md => HTML => AMP Optimizer => valid AMP
const AmpOptimizer = require('@ampproject/toolbox-optimizer'); const md = require('markdown-it')({ // don't sanitize html if you want to support AMP components in Markdown html: true, }); // enable markdown mode const ampOptimizer = AmpOptimizer.create({ markdown: true, }); const markdown = ` # Markdown Here is an image declared in Markdown syntax: ![A random image](https://unsplash.it/800/600). You can directly declare AMP components: <amp-twitter width="375" height="472" layout="responsive" data-tweetid="1182321926473162752"> </amp-twitter> Any missing extensions will be automatically imported. `; const html = md.render(markdown); // valid AMP! const amphtml = await ampOptimizer.transformHtml(html, { canonical: filePath, });
GeoJsonLayer
GeoJsonLayer layer = new GeoJsonLayer( map, geoJsonFile, null, polygonManager, null, groundOverlayManager );
GeoJsonLayer layer = new GeoJsonLayer(
map,
geoJsonFile,
null,
polygonManager,
groundOverlayManager
);
null
val layer = GeoJsonLayer( map = map, geoJsonFile = geoJsonFile, polygonManager = polygonManager, groundOverlayManager = groundOverlayManager )
val layer = GeoJsonLayer(
map = map,
geoJsonFile = geoJsonFile,
polygonManager = polygonManager,
groundOverlayManager = groundOverlayManager
)
上記のトラックに関する一般的な補足事項
上記のいずれかのトラックを経て本番環境に移行できるのは、1 つのバージョンのみです。
テストトラックで公開するアーティファクトには、Play Console でテスト プログラムをオプトインしたユーザーがアクセスできます。
各トラックには、1 つの Android App Bundle または 1 つの APK をアップロードできます。
注: 現在、端末上の複数アカウントに関する制限があることは承知しています。 これを回避するには、すべてのアカウントが内部アプリ共有にアクセスできるようにするか、メールアドレス一覧にないテスターも Play Console でダウンロードできるようにします。
https://play.google.com/apps/test/<package name>/<version code>
const pos1 = {lat: -33.727111, lng: 150.371124}; const marker1 = new google.maps.Marker({position: pos1, map: map}); const pos2 = {lat: -33.718234, lng: 150.363181}; const marker2 = new google.maps.Marker({position: pos2, map: map});
const pos1 = {lat: -33.727111, lng: 150.371124};
const marker1 = new google.maps.Marker({position: pos1, map: map});
const pos2 = {lat: -33.718234, lng: 150.363181};
const marker2 = new google.maps.Marker({position: pos2, map: map});
<script src="https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js">
// すべてのマーカーの配列を作る const markers = [marker1, marker2]; // 追加するクラスターアイコンへのパスを定義する (1.png, 2.png, ...) const imagePath = "https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m"; // マップとマーカーに対してクラスター化を有効にする const markerClusterer = new MarkerClusterer(map, markers, {imagePath: imagePath});
// すべてのマーカーの配列を作る
const markers = [marker1, marker2];
// 追加するクラスターアイコンへのパスを定義する (1.png, 2.png, ...)
const imagePath = "https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m";
// マップとマーカーに対してクラスター化を有効にする
const markerClusterer = new MarkerClusterer(map, markers, {imagePath: imagePath});
imagePath
.png
imageExtension
gridSize
zoomOnClick
maxZoom
styles
// オプションの定義 const clusterOptions = { imagePath: "https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m", gridSize: 30, zoomOnClick: false, maxZoom: 10, }; // マーカーを管理する Marker Clusterer を追加する const markerClusterer = new MarkerClusterer(map, markers, clusterOptions); // クラスターが生成されたあとにスタイルを変更する const styles = markerClusterer.getStyles(); for (let i=0; i<styles.length; i++) { styles[i].textColor = "red"; styles[i].textSize = 18; }
// オプションの定義
const clusterOptions = {
imagePath: "https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m",
gridSize: 30,
zoomOnClick: false,
maxZoom: 10,
};
// マーカーを管理する Marker Clusterer を追加する
const markerClusterer = new MarkerClusterer(map, markers, clusterOptions);
// クラスターが生成されたあとにスタイルを変更する
const styles = markerClusterer.getStyles();
for (let i=0; i<styles.length; i++) {
styles[i].textColor = "red";
styles[i].textSize = 18;
}
<amp-lightbox layout="nodisplay" on="lightboxOpen:searchInput.focus; lightboxClose:searchTriggerOpen.focus" scrollable>
/search/do
{ "result": { "pageCount": 10, "pages": [ { "title": "Some title", "description": "Description", "url": "http://amp.dev/some/link" }, ... ] }, "nextUrl": "/search/do?q=amp&page=2" }
<amp-list>
[src]
<amp-mustache>
<amp-list id="searchList" src="/search/initial-items" [src]="query ? '/search/initial-items : '/search/do?q=' + encodeURIComponent(query)" binding="no" items="." height="80vh" layout="fixed-height" load-more="auto" load-more-bookmark="nextUrl" reset-on-refresh single-item> <template type="amp-mustache"> <div class="search-result-list"> {{#result.pages}} <a href="{{url}}"> <h4>{{title}}</h4> <p>{{description}}</p> </a> {{/result.pages}} </div> </template> </amp-list>
<amp-autocomplete>
/search/autosuggest
filter="substring"
<amp-autocomplete filter="substring" min-characters="1" on="select:AMP.setState({ query: event.value })" submit-on-enter="false" src="/search/autosuggest" > <input placeholder="What are you looking for?"> </amp-autocomplete>
query
<amp-list [src]="'/search/do?q=' + encodeURIComponent(query) + '&locale=en'">
select
amp-autocomplete
on
amp-list
<form action-xhr="/search/echo" on="submit: AMP.setState({ query: queryInput }), searchResult.focus, searchList.changeToLayoutContainer" method="POST" target="_top"> <amp-autocomplete filter="substring" min-characters="1" on="select:AMP.setState({ query: event.value })" submit-on-enter="false" src="/search/autosuggest" > <input id="searchInput" placeholder="What are you looking for?" on="input-throttled:AMP.setState({ queryInput: event.value })" > <button disabled [disabled]="!queryInput">Search</button> </amp-autocomplete> </form>
changeToLayoutContainer
submit
/search/latest-query
amp-state
// search.html <amp-state id="query" src="/search/latest-query"></amp-state> <amp-list src="/search/initial-items" [src]="query ? '/search/initial-items : '/search/do?q=' + encodeURIComponent(query)" ... </amp-list>
// serviceworker.js async function searchDoRequestHandler(url, request) { const searchQuery = decodeURIComponent(url.search.match(/q=([^&]+)/)[1]); const cache = await caches.open(SEARCH_CACHE_NAME); cache.put(SEARCH_LATEST_QUERY_PATH, new Response(`"${searchQuery}"`)); let response = await cache.match(request); if (response) return response; response = await fetch(request); if (response.status == 200) { cache.delete(request, { ignoreSearch: true, }); cache.put(request, response.clone()); } return response; }
// serviceworker.js self.addEventListener('fetch', (event) => { const requestUrl = new URL(event.request.url); if (requestUrl.pathname === '/search/do') { event.respondWith(searchDoRequestHandler(requestUrl, event.request)); } });
<amp-state>
if ("NDEFWriter" in window) { const writer = new NDEFWriter(); await writer.write("Hello world!"); }
if ("NDEFReader" in window) { const reader = new NDEFReader(); await reader.scan(); reader.onreading = ({ message }) => { console.log(`Message read from a NFC tag: ${message}`); }; }