Deneysel YouTube Hazır Oyunlar Flutter Web SDK'sı

Bu kılavuzda, YouTube Playables SDK'sının Flutter web uygulamasıyla nasıl kullanılacağı açıklanmaktadır.

Flutter Yapılandırması

Varsayılan Flutter web uygulamasının oynanabilir olarak çalışması için bazı değişiklikler yapılması gerekir. Flutter 3.24.5 sürümü itibarıyla aşağıdaki değişiklikler gereklidir.

Flutter varsayılan olarak kaynakları kök dizinden yükleyecek şekilde yapılandırılmıştır. Oynanabilirler ise kaynakların giriş noktasına göre yüklenmesini gerektirir. Bu durum, 404 hatası olarak veya JavaScript konsolunda şu hatayı göstererek ortaya çıkar: Refused to execute script from... because its MIME type ('text/html') is not executable. Bu sorunu yeniden yapılandırmanın bir yolu, index.html dosyasından aşağıdaki etiketi kaldırmaktır:

<base href="$FLUTTER_BASE_HREF">

Flutter, varsayılan olarak bazı kitaplıkları uygulamaya yerleştirmek yerine dinamik olarak yükler. CanvasKit buna örnek gösterilebilir. Bu durum, JavaScript konsolunda şu hatayla gösterilir: Refused to connect to '...' because it violates the following Content Security Policy directive: "connect-src 'self' blob: data:". Flutter, yükleme başarısız olduğunda Failed to download any of the following CanvasKit URLs gibi ek günlükler de yayınlayabilir. Bu sorunu düzeltmek için web uygulamanızı aşağıdaki ek işaretle derleyebilirsiniz:

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

Flutter web uygulamaları varsayılan olarak yazı tiplerini uygulamaya yerleştirmek yerine dinamik olarak yükler. Varsayılan Flutter uygulaması Roboto yazı tipini kullanır ve bu, JavaScript konsolunda şu hatayla gösterilir: Refused to connect to '...' because it violates the following Content Security Policy directive: "connect-src 'self' blob: data". Bu sorunu düzeltmek için aşağıdaki adımları uygulayabilirsiniz:

  • Flutter web uygulamanızın kökünde "fonts" adlı bir klasör oluşturun
  • Roboto yazı tipi ailesini fonts.google.com adresinden indirin.
  • Yazı tiplerini ayıklayın ve Roboto-Regular.ttf dosyasını yazı tipi klasörüne kopyalayın.
  • Flutter web uygulamanızın pubspec.yaml dosyasına aşağıdaki satırı ekleyin:
  fonts:
    - family: Roboto
      fonts:
       - asset: fonts/Roboto-Regular.ttf

Yazı tipi yapılandırması hakkında daha fazla bilgiyi Flutter belgelerinde bulabilirsiniz.

SDK Entegrasyonu

YouTube Playables SDK'sı, aşağıdakine benzer bir JS birlikte çalışabilirlik sarmalayıcısı aracılığıyla Flutter web oyunlarından kullanılabilir:

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;

Kullanım

  1. Web SDK'sını ayarlama ve başlatma talimatlarını uygulayın.
    • Flutter web uygulamanızın web/index.html dosyasını, gerekli komut dosyası etiketlerini içerecek şekilde değiştirin.
  2. Flutter web uygulamanızın src bölümüne ytgame.dart dosyasının bir kopyasını ekleyin.
  3. js paketinin Flutter'a eklendiğinden emin olun (ör. flutter pub add js komutunu çalıştırarak).
  4. Gerektiğinde import 'src/location/of/ytgame.dart'; ekleyin.
  5. SDK'ya erişmek için ytgame nesnesini kullanın.

Örnekler

Dart API'nin kullanıldığı birkaç örneği aşağıda bulabilirsiniz.

firstFrameReady ve gameReady

Oyununuzun doğru şekilde başlaması için ilk kare görüntülenmeye hazır olduğunda firstFrameReadyve oyunla etkileşim kurulmaya hazır olduğunda gameReady çağrısını yapmanız gerekir. Varsayılan Flutter uygulamasında bunu yapmanın bir yolu, çağrıları main.dart dosyasına eklemektir:

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

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

Bunun, oyununuzu çalıştırmak için kullanabileceğiniz bir örnek olduğunu unutmayın. Doğru şekilde uygulamak için bu çağrıların konumunu uygun şekilde yerleştirmeniz gerekir.

sendScore

Bu örnekte Dart'ta sendScore(int points)'ün bir uygulaması gösterilmektedir:

...
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

Aşağıda, bir oyunun gerektiğinde motorunu duraklatmak için YT Playables'den gelen Pause etkinliklerini nasıl dinleyebileceğine dair bir örnek verilmiştir:

...
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();
}

YT Playables API referansının tamamına bakın.