Eksperymentalny pakiet SDK Flutter Web Pokoju gier YouTube

Z tego przewodnika dowiesz się, jak używać pakietu SDK YouTube Playables w aplikacji internetowej Flutter.

Konfiguracja Fluttera

Domyślna aplikacja internetowa Flutter wymaga pewnych zmian, aby działać jako Playable. Począwszy od wersji 3.24.5 Fluttera wymagane są następujące zmiany:

Domyślnie Flutter wczytuje zasoby z katalogu głównego, a Playables wymaga, aby zasoby były wczytywane względem punktu wejścia. Wyświetli się on jako błąd 404 lub jako błąd w konsoli JavaScriptu: Refused to execute script from... because its MIME type ('text/html') is not executable.. Aby to zmienić, usuń z pliku index.html ten tag:

<base href="$FLUTTER_BASE_HREF">

Domyślnie Flutter wczytuje niektóre biblioteki dynamicznie zamiast umieszczać je w aplikacji. Przykładem takiej biblioteki jest CanvasKit. W konsoli JavaScriptu pojawi się komunikat o błędzie: Refused to connect to '...' because it violates the following Content Security Policy directive: "connect-src 'self' blob: data:". Flutter może też wygenerować dodatkowe dzienniki, takie jak Failed to download any of the following CanvasKit URLs. Aby to naprawić, możesz skompilować aplikację internetową z dodatkowym flagą:

$ flutter build web --no-web-resources-cdn

Domyślnie aplikacje internetowe Flutter ładują czcionki dynamicznie zamiast umieszczać je w aplikacji. Domyślna aplikacja Flutter używa czcionki Roboto, która będzie widoczna jako błąd w konsoli JavaScriptu: Refused to connect to '...' because it violates the following Content Security Policy directive: "connect-src 'self' blob: data". Aby to naprawić, wykonaj te czynności:

  • Utwórz w katalogu głównym aplikacji internetowej Flutter folder o nazwie „fonts”.
  • Pobierz rodzinę czcionek Roboto ze strony fonts.google.com.
  • Wyodrębnij czcionki i skopiuj plik Roboto-Regular.ttf do folderu czcionek.
  • Dodaj ten wiersz do pliku pubspec.yaml aplikacji internetowej Flutter:
  fonts:
    - family: Roboto
      fonts:
       - asset: fonts/Roboto-Regular.ttf

Więcej informacji o konfigurowaniu czcionek znajdziesz w dokumentacji Fluttera.

Integracja pakietu SDK

Pakiet SDK YouTube Playables można używać w grze internetowej w Flutterze za pomocą owijarki JS-interop podobnej do tej:

ytgame.dart

// Copyright 2023 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@JS()
library ytgame_api;

import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;

enum PlayablesErrorType {
  unknown('UNKNOWN'),
  apiUnavailable('API_UNAVAILABLE'),
  invalidParams('INVALID_PARAMS'),
  sizeLimitExceeded('SIZE_LIMIT_EXCEEDED');

  const PlayablesErrorType(String type) : _type = type;
  final String _type;

  @override
  String toString() => _type;

  static PlayablesErrorType fromString(String errorType) {
    return values.firstWhere(
      (element) => element._type == errorType,
    );
  }
}

@JS()
@staticInterop
class PlayablesError {}

extension PlayablesErrorExtension on PlayablesError {
  @JS('errorType')
  external String get _errorType;
  PlayablesErrorType get errorType {
    return PlayablesErrorType.fromString(_errorType);
  }
}

@JS()
@anonymous
@staticInterop
class PlayablesScore {
  external factory PlayablesScore({
    required num value,
  });
}

extension PlayablesScoreExtension on PlayablesScore {
  external num get value;
}

// engagement
@JS()
@staticInterop
class PlayablesEngagement {}

extension PlayablesEngagementExtension on PlayablesEngagement {
  @JS('sendScore')
  external Object _sendScore(PlayablesScore score);
  Future<void> sendScore(PlayablesScore score) {
    return js_util.promiseToFuture(_sendScore(score));
  }
}

// game
@JS()
@staticInterop
class PlayablesGame {}

extension PlayablesGameExtension on PlayablesGame {
  external void firstFrameReady();
  external void gameReady();

  @JS('loadData')
  external Object _loadData();
  Future<String?> loadData() {
    return js_util.promiseToFuture<String?>(_loadData());
  }

  @JS('saveData')
  external Object _saveData(String? data);
  Future<void> saveData(String? data) {
    return js_util.promiseToFuture<void>(_saveData(data));
  }
}

// health
@JS()
@staticInterop
class PlayablesHealth {}

extension PlayablesHealthExtension on PlayablesHealth {
  external void logError();
  external void logWarning();
}

// system
typedef PlayablesUnsetCallback = void Function();
typedef PlayablesOnAudioEnabledChange = void Function(bool isAudioEnabled);
typedef PlayablesOnPause = void Function();
typedef PlayablesOnResume = void Function();

@JS()
@staticInterop
class PlayablesSystem {}

extension PlayablesSystemExtension on PlayablesSystem {
  external bool isAudioEnabled();

  @JS('onAudioEnabledChange')
  external PlayablesUnsetCallback _onAudioEnabledChange(
      PlayablesOnAudioEnabledChange callback);
  PlayablesUnsetCallback onAudioEnabledChange(
      PlayablesOnAudioEnabledChange callback) {
    return _onAudioEnabledChange(allowInterop(callback));
  }

  @JS('onPause')
  external PlayablesUnsetCallback _onPause(PlayablesOnPause callback);
  PlayablesUnsetCallback onPause(PlayablesOnPause callback) {
    return _onPause(allowInterop(callback));
  }

  @JS('onResume')
  external PlayablesUnsetCallback _onResume(PlayablesOnResume callback);
  PlayablesUnsetCallback onResume(PlayablesOnResume callback) {
    return _onResume(allowInterop(callback));
  }
}

@JS()
@staticInterop
class Playables {}

extension PlayablesExtension on Playables {
  @JS('SDK_VERSION')
  external String get sdkVersion;
  external PlayablesEngagement get engagement;
  external PlayablesGame get game;
  external PlayablesHealth get health;
  external PlayablesSystem get system;
}

@JS('ytgame')
external Playables get ytgame;

Wykorzystanie

  1. Postępuj zgodnie z instrukcjami, aby skonfigurować i zainicjować pakiet SDK internetowego.
    • Zmodyfikuj web/index.html swojej aplikacji Flutter Web, aby uwzględnić wymagane tagi skryptu.
  2. Dodaj kopię pliku ytgame.dart w folderze src aplikacji internetowej Flutter.
  3. Upewnij się, że pakiet js został dodany do Fluttera, np. przez uruchomienie polecenia flutter pub add js.
  4. W razie potrzeby dodaj import 'src/location/of/ytgame.dart';.
  5. Aby uzyskać dostęp do pakietu SDK, użyj obiektu ytgame.

Przykłady

Poniżej znajdziesz kilka przykładów użycia interfejsu API Dart.

firstFrameReadygameReady

Aby gra mogła się prawidłowo uruchomić, musisz wywołać funkcję firstFrameReady, gdy pierwszy kadr jest gotowy do wyświetlenia, oraz funkcję gameReady, gdy gra jest gotowa do interakcji. Jednym ze sposobów wykonania tego w domyślnej aplikacji Flutter jest dodanie wywołań do pliku main.dart:

...
import 'src/ytgame.dart';
...

void main() {
  ytgame.game.firstFrameReady();
  ytgame.game.gameReady();
  runApp(const MyApp());
}

Pamiętaj, że jest to tylko przykładowy kod, który pozwoli uruchomić grę. Aby zapewnić prawidłową implementację, musisz odpowiednio umieścić te wywołania.

sendScore

Ten przykład pokazuje implementację sendScore(int points) w Dart:

...
import 'src/ytgame.dart';
...

/// Sends [points] as score to YT Playables
Future<void> sendScore(int points) async {
  // Create a score object...
  final PlayablesScore score = PlayablesScore(
    value: points,
  );
  // Submit the score to ytgame
  await ytgame.engagement.sendScore(score);
}

onPause

Oto przykład tego, jak gra może nasłuchiwać zdarzeń Pause pochodzących z YT Playables, aby w razie potrzeby wstrzymać silnik:

...
import 'src/ytgame.dart';
...

/// The instance of your game.
late Game myGame;

/// Callback to stop listening for Pause events (cleanup).
PlayablesUnsetCallback? _unsetOnPause;
...

/// Sets a [callback] to respond to Pause events.
void registerOnPauseListener(PlayablesOnPause callback) {
  _unsetOnPause = ytgame.system.onPause(callback);
}

/// Stops listening to Pause events, and clears [_unsetOnPause].
void unsetOnPauseListener() {
  if (_unsetOnPause != null) {
    _unsetOnPause!();
    _unsetOnPause = null;
  }
}

...

/// Initialization for your game
void initGame() {
  ...
  myGame = ...
  registerOnPauseListener(_onPause);
}

void _onPause() {
  // This is called when a Pause event is received from YT Playables.
  myGame.pauseEngine();
}

/// Teardown for your game
void disposeGame() {
  ...
  unsetOnPauseListener();
}

Zapoznaj się z pełną dokumentacją interfejsu YT Playables API.