dev
cpdl 3 months ago
parent 24fc4c113d
commit a8f3ce3307
  1. 38
      example/pubspec.lock
  2. 2
      example/pubspec.yaml
  3. 13
      lib/flutter_cocos_view.dart
  4. 17
      lib/flutter_cocos_view_method_channel.dart
  5. 29
      lib/flutter_cocos_view_platform_interface.dart
  6. 103
      lib/src/facade_controller.dart
  7. 89
      lib/src/facade_widget.dart
  8. 36
      lib/src/helpers/events.dart
  9. 27
      lib/src/helpers/misc.dart
  10. 31
      lib/src/helpers/types.dart
  11. 201
      lib/src/io/cocos_widget.dart
  12. 154
      lib/src/io/cocos_widget_platform.dart
  13. 299
      lib/src/io/device_method.dart
  14. 4
      lib/src/io/io.dart
  15. 213
      lib/src/io/mobile_cocos_widget_controller.dart
  16. 20
      lib/src/io/windows_cocos_widget_view.dart
  17. 3
      pubspec.yaml
  18. 27
      test/flutter_cocos_view_method_channel_test.dart
  19. 29
      test/flutter_cocos_view_test.dart

@ -109,18 +109,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
@ -149,18 +149,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.15.0"
version: "1.12.0"
path:
dependency: transitive
description:
@ -173,10 +173,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
@ -222,6 +222,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
url: "https://pub.dev"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description:
@ -250,10 +258,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.0"
vector_math:
dependency: transitive
description:
@ -266,10 +274,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.2.1"
webdriver:
dependency: transitive
description:
@ -279,5 +287,5 @@ packages:
source: hosted
version: "3.0.3"
sdks:
dart: ">=3.5.4 <4.0.0"
dart: ">=3.4.4 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

@ -5,7 +5,7 @@ description: "Demonstrates how to use the flutter_cocos_view plugin."
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: ^3.5.4
sdk: ^3.4.4
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions

@ -5,10 +5,11 @@
// platforms in the `pubspec.yaml` at
// https://flutter.dev/to/pubspec-plugin-platforms.
import 'flutter_cocos_view_platform_interface.dart';
library flutter_Cocos_widget;
class FlutterCocosView {
Future<String?> getPlatformVersion() {
return FlutterCocosViewPlatform.instance.getPlatformVersion();
}
}
export 'src/facade_controller.dart';
export 'src/facade_widget.dart'
if (dart.library.io) 'src/io/cocos_widget.dart';
export 'src/helpers/events.dart';
export 'src/helpers/misc.dart';
export 'src/helpers/types.dart';

@ -1,17 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'flutter_cocos_view_platform_interface.dart';
/// An implementation of [FlutterCocosViewPlatform] that uses method channels.
class MethodChannelFlutterCocosView extends FlutterCocosViewPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('flutter_cocos_view');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
}

@ -1,29 +0,0 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'flutter_cocos_view_method_channel.dart';
abstract class FlutterCocosViewPlatform extends PlatformInterface {
/// Constructs a FlutterCocosViewPlatform.
FlutterCocosViewPlatform() : super(token: _token);
static final Object _token = Object();
static FlutterCocosViewPlatform _instance = MethodChannelFlutterCocosView();
/// The default instance of [FlutterCocosViewPlatform] to use.
///
/// Defaults to [MethodChannelFlutterCocosView].
static FlutterCocosViewPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [FlutterCocosViewPlatform] when
/// they register themselves.
static set instance(FlutterCocosViewPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}

@ -0,0 +1,103 @@
typedef void CocosCreatedCallback(CocosWidgetController controller);
abstract class CocosWidgetController {
static dynamic webRegistrar;
/// Method required for web initialization
static void registerWith(dynamic registrar) {
webRegistrar = registrar;
}
/// Initialize [CocosWidgetController] with [id]
/// Mainly for internal use when instantiating a [CocosWidgetController] passed
/// in [CocosWidget.onCocosCreated] callback.
static Future<CocosWidgetController> init(
int id, dynamic CocosWidgetState) async {
throw UnimplementedError('init() has not been implemented.');
}
/// Checks to see if Cocos player is ready to be used
/// Returns `true` if Cocos player is ready.
Future<bool?>? isReady() {
throw UnimplementedError('isReady() has not been implemented.');
}
/// Get the current pause state of the Cocos player
/// Returns `true` if Cocos player is paused.
Future<bool?>? isPaused() {
throw UnimplementedError('isPaused() has not been implemented.');
}
/// Get the current load state of the Cocos player
/// Returns `true` if Cocos player is loaded.
Future<bool?>? isLoaded() {
throw UnimplementedError('isLoaded() has not been implemented.');
}
/// Helper method to know if Cocos has been put in background mode (WIP) unstable
/// Returns `true` if Cocos player is in background.
Future<bool?>? inBackground() {
throw UnimplementedError('inBackground() has not been implemented.');
}
/// Creates a Cocos player if it's not already created. Please only call this if Cocos is not ready,
/// or is in unloaded state. Use [isLoaded] to check.
/// Returns `true` if Cocos player was created succesfully.
Future<bool?>? create() {
throw UnimplementedError('create() has not been implemented.');
}
/// Post message to Cocos from flutter. This method takes in a string [message].
/// The [gameObject] must match the name of an actual Cocos game object in a scene at runtime, and the [methodName],
/// must exist in a `MonoDevelop` `class` and also exposed as a method. [message] is an parameter taken by the method
///
/// ```dart
/// postMessage("GameManager", "openScene", "ThirdScene")
/// ```
Future<void>? postMessage(String gameObject, methodName, message) {
throw UnimplementedError('postMessage() has not been implemented.');
}
/// Post message to Cocos from flutter. This method takes in a Json or map structure as the [message].
/// The [gameObject] must match the name of an actual Cocos game object in a scene at runtime, and the [methodName],
/// must exist in a `MonoDevelop` `class` and also exposed as a method. [message] is an parameter taken by the method
///
/// ```dart
/// postJsonMessage("GameManager", "openScene", {"buildIndex": 3, "name": "ThirdScene"})
/// ```
Future<void>? postJsonMessage(
String gameObject, String methodName, Map<String, dynamic> message) {
throw UnimplementedError('postJsonMessage() has not been implemented.');
}
/// Pause the Cocos in-game player with this method
Future<void>? pause() {
throw UnimplementedError('pause() has not been implemented.');
}
/// Resume the Cocos in-game player with this method idf it is in a paused state
Future<void>? resume() {
throw UnimplementedError('resume() has not been implemented.');
}
/// Sometimes you want to open Cocos in it's own process and openInNativeProcess does just that.
/// It works for Android and iOS is WIP
Future<void>? openInNativeProcess() {
throw UnimplementedError('openInNativeProcess() has not been implemented.');
}
/// Unloads Cocos player from th current process (Works on Android only for now)
/// iOS is WIP. For more information please read [Cocos Docs](https://docs.Cocos3d.com/2020.2/Documentation/Manual/CocosasaLibrary.html)
Future<void>? unload() {
throw UnimplementedError('unload() has not been implemented.');
}
/// Quits Cocos player. Note that this kills the current flutter process, thus quiting the app
Future<void>? quit() {
throw UnimplementedError('quit() has not been implemented.');
}
void dispose() {
throw UnimplementedError('dispose() has not been implemented.');
}
}

@ -0,0 +1,89 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_cocos_view/src/facade_controller.dart';
import 'helpers/misc.dart';
class CocosWidget extends StatefulWidget {
CocosWidget({
Key? key,
required this.onCocosCreated,
this.onCocosMessage,
this.fullscreen = false,
this.enablePlaceholder = false,
this.runImmediately = false,
this.unloadOnDispose = false,
this.printSetupLog = true,
this.onCocosUnloaded,
this.gestureRecognizers,
this.placeholder,
this.useAndroidViewSurface = false,
this.onCocosSceneLoaded,
this.uiLevel = 1,
this.borderRadius = BorderRadius.zero,
this.layoutDirection,
this.hideStatus = false,
});
///Event fires when the Cocos player is created.
final CocosCreatedCallback onCocosCreated;
///Event fires when the [CocosWidget] gets a message from Cocos.
final CocosMessageCallback? onCocosMessage;
///Event fires when the [CocosWidget] gets a scene loaded from Cocos.
final CocosSceneChangeCallback? onCocosSceneLoaded;
///Event fires when the [CocosWidget] Cocos player gets unloaded.
final CocosUnloadCallback? onCocosUnloaded;
final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
/// Set to true to force Cocos to fullscreen
final bool fullscreen;
/// Set to true to force Cocos to fullscreen
final bool hideStatus;
/// Controls the layer in which Cocos widget is rendered in flutter (defaults to 1)
final int uiLevel;
/// This flag tells android to load Cocos as the flutter app starts (Android only)
final bool runImmediately;
/// This flag tells android to unload Cocos whenever widget is disposed
final bool unloadOnDispose;
/// This flag enables placeholder widget
final bool enablePlaceholder;
/// This flag enables placeholder widget
final bool printSetupLog;
/// This flag allows you use AndroidView instead of PlatformViewLink for android
final bool? useAndroidViewSurface;
/// This is just a helper to render a placeholder widget
final Widget? placeholder;
/// Border radius
final BorderRadius borderRadius;
/// The layout direction to use for the embedded view.
///
/// If this is null, the ambient [Directionality] is used instead. If there is
/// no ambient [Directionality], [TextDirection.ltr] is used.
final TextDirection? layoutDirection;
@override
_CocosWidgetState createState() => _CocosWidgetState();
}
class _CocosWidgetState extends State<CocosWidget> {
@override
Widget build(BuildContext context) {
return Text(
'$defaultTargetPlatform is not yet supported by the Cocos player plugin');
}
}

@ -0,0 +1,36 @@
import 'package:flutter_cocos_view/src/helpers/types.dart';
class CocosEvent<T> {
/// The ID of the Cocos this event is associated to.
final int cocosId;
/// The value wrapped by this event
final T value;
/// Build a Cocos Event, that relates a mapId with a given value.
///
/// The `cocosId` is the id of the map that triggered the event.
/// `value` may be `null` in events that don't transport any meaningful data.
CocosEvent(this.cocosId, this.value);
}
class CocosSceneLoadedEvent extends CocosEvent<SceneLoaded?> {
CocosSceneLoadedEvent(int cocosId, SceneLoaded? value)
: super(cocosId, value);
}
class CocosLoadedEvent extends CocosEvent<void> {
CocosLoadedEvent(int cocosId, void value) : super(cocosId, value);
}
class CocosUnLoadedEvent extends CocosEvent<void> {
CocosUnLoadedEvent(int cocosId, void value) : super(cocosId, value);
}
class CocosCreatedEvent extends CocosEvent<void> {
CocosCreatedEvent(int cocosId, void value) : super(cocosId, value);
}
class CocosMessageEvent extends CocosEvent<dynamic> {
CocosMessageEvent(int cocosId, dynamic value) : super(cocosId, value);
}

@ -0,0 +1,27 @@
import 'package:flutter_cocos_view/src/helpers/types.dart';
/// Error thrown when an unknown cocos ID is provided to a method channel API.
class UnknownCocosIDError extends Error {
/// Creates an assertion error with the provided [cocosId] and optional
/// [message].
UnknownCocosIDError(this.cocosId, [this.message]);
/// The unknown ID.
final int cocosId;
/// Message describing the assertion error.
final Object? message;
String toString() {
if (message != null) {
return "Unknown cocos ID $cocosId: ${Error.safeToString(message)}";
}
return "Unknown cocos ID $cocosId";
}
}
typedef void CocosMessageCallback(dynamic handler);
typedef void CocosSceneChangeCallback(SceneLoaded? message);
typedef void CocosUnloadCallback();

@ -0,0 +1,31 @@
class SceneLoaded {
final String? name;
final int? buildIndex;
final bool? isLoaded;
final bool? isValid;
SceneLoaded({this.name, this.buildIndex, this.isLoaded, this.isValid});
/// Mainly for internal use when calling [CameraUpdate.newCameraPosition].
dynamic toMap() => <String, dynamic>{
'name': name,
'buildIndex': buildIndex,
'isLoaded': isLoaded,
'isValid': isValid,
};
/// Deserializes [SceneLoaded] from a map.
///
/// Mainly for internal use.
static SceneLoaded? fromMap(dynamic json) {
if (json == null) {
return null;
}
return SceneLoaded(
name: json['name'],
buildIndex: json['buildIndex'],
isLoaded: json['isLoaded'],
isValid: json['isValid'],
);
}
}

@ -0,0 +1,201 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../facade_controller.dart';
import '../helpers/misc.dart';
import 'device_method.dart';
import 'mobile_cocos_widget_controller.dart';
import 'cocos_widget_platform.dart';
int _nextCocosCreationId = 0;
/// Android specific settings for [CocosWidget].
class AndroidCocosWidgetFlutter {
/// Whether to render [CocosWidget] with a [AndroidViewSurface] to build the Flutter Cocos widget.
///
/// This implementation uses hybrid composition to render the Flutter Cocos
/// Widget on Android. This comes at the cost of some performance on Android
/// versions below 10. See
/// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more
/// information.
///
/// Defaults to true.
static bool get useAndroidViewSurface {
final CocosWidgetPlatform platform = CocosWidgetPlatform.instance;
if (platform is MethodChannelCocosWidget) {
return platform.useAndroidViewSurface;
}
return false;
}
/// Set whether to render [CocosWidget] with a [AndroidViewSurface] to build the Flutter Cocos widget.
///
/// This implementation uses hybrid composition to render the Cocos Widget
/// Widget on Android. This comes at the cost of some performance on Android
/// versions below 10. See
/// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more
/// information.
///
/// Defaults to true.
static set useAndroidViewSurface(bool useAndroidViewSurface) {
final CocosWidgetPlatform platform = CocosWidgetPlatform.instance;
if (platform is MethodChannelCocosWidget) {
platform.useAndroidViewSurface = useAndroidViewSurface;
}
}
}
typedef MobileCocosWidgetState = _CocosWidgetState;
class CocosWidget extends StatefulWidget {
CocosWidget({
Key? key,
required this.onCocosCreated,
this.onCocosMessage,
this.fullscreen = false,
this.enablePlaceholder = false,
this.runImmediately = false,
this.unloadOnDispose = false,
this.printSetupLog = true,
this.onCocosUnloaded,
this.gestureRecognizers,
this.placeholder,
this.useAndroidViewSurface = false,
this.onCocosSceneLoaded,
this.uiLevel = 1,
this.borderRadius = BorderRadius.zero,
this.layoutDirection,
this.hideStatus = false,
this.webUrl,
});
///Event fires when the Cocos player is created.
final CocosCreatedCallback onCocosCreated;
/// WebGL url source.
final String? webUrl;
///Event fires when the [CocosWidget] gets a message from Cocos.
final CocosMessageCallback? onCocosMessage;
///Event fires when the [CocosWidget] gets a scene loaded from Cocos.
final CocosSceneChangeCallback? onCocosSceneLoaded;
///Event fires when the [CocosWidget] Cocos player gets unloaded.
final CocosUnloadCallback? onCocosUnloaded;
final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
/// Set to true to force Cocos to fullscreen
final bool fullscreen;
/// Set to true to force Cocos to fullscreen
final bool hideStatus;
/// Controls the layer in which Cocos widget is rendered in flutter (defaults to 1)
final int uiLevel;
/// This flag tells android to load Cocos as the flutter app starts (Android only)
final bool runImmediately;
/// This flag tells android to unload Cocos whenever widget is disposed
final bool unloadOnDispose;
/// This flag enables placeholder widget
final bool enablePlaceholder;
/// This flag enables placeholder widget
final bool printSetupLog;
/// This flag allows you use AndroidView instead of PlatformViewLink for android
final bool? useAndroidViewSurface;
/// This is just a helper to render a placeholder widget
final Widget? placeholder;
/// Border radius
final BorderRadius borderRadius;
/// The layout direction to use for the embedded view.
///
/// If this is null, the ambient [Directionality] is used instead. If there is
/// no ambient [Directionality], [TextDirection.ltr] is used.
final TextDirection? layoutDirection;
@override
_CocosWidgetState createState() => _CocosWidgetState();
}
class _CocosWidgetState extends State<CocosWidget> {
late int _CocosId;
CocosWidgetController? _controller;
@override
void initState() {
super.initState();
if (!kIsWeb) {
_CocosId = _nextCocosCreationId++;
} else {
_CocosId = 0;
}
}
@override
Future<void> dispose() async {
if (!kIsWeb) {
if (_nextCocosCreationId > 0) --_nextCocosCreationId;
}
try {
_controller?.dispose();
_controller = null;
} catch (e) {
// todo: implement
}
super.dispose();
}
@override
Widget build(BuildContext context) {
final Map<String, dynamic> CocosOptions = <String, dynamic>{
'fullscreen': widget.fullscreen,
'uiLevel': widget.uiLevel,
'hideStatus': widget.hideStatus,
'unloadOnDispose': widget.unloadOnDispose,
'runImmediately': widget.runImmediately,
};
if (widget.enablePlaceholder) {
return widget.placeholder ??
Text('Placeholder mode enabled, no native code will be called');
}
return CocosWidgetPlatform.instance.buildViewWithTextDirection(
_CocosId,
_onPlatformViewCreated,
cocosOptions: CocosOptions,
textDirection: widget.layoutDirection ??
Directionality.maybeOf(context) ??
TextDirection.ltr,
gestureRecognizers: widget.gestureRecognizers,
useAndroidViewSurf: widget.useAndroidViewSurface,
cocosSrcUrl: widget.webUrl,
);
}
Future<void> _onPlatformViewCreated(int id) async {
final controller = await MobileCocosWidgetController.init(id, this);
_controller = controller;
widget.onCocosCreated(controller);
if (widget.printSetupLog) {
log('*********************************************');
log('** flutter Cocos controller setup complete **');
log('*********************************************');
}
}
}

@ -0,0 +1,154 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import '../helpers/events.dart';
import 'device_method.dart';
abstract class CocosWidgetPlatform extends PlatformInterface {
/// Constructs a CocosViewFlutterPlatform.
CocosWidgetPlatform() : super(token: _token);
static final Object _token = Object();
static CocosWidgetPlatform _instance = MethodChannelCocosWidget();
/// The default instance of [CocosWidgetPlatform] to use.
///
/// Defaults to [MethodChannelCocosWidgetFlutter].
static CocosWidgetPlatform get instance => _instance;
/// Platform-specific plugins should set this with their own platform-specific
/// class that extends [CocosWidgetPlatform] when they register themselves.
static set instance(CocosWidgetPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// /// Initializes the platform interface with [id].
///
/// This method is called when the plugin is first initialized.
Future<void> init(int cocosId) {
throw UnimplementedError('init() has not been implemented.');
}
Future<bool?> isReady({required int cocosId}) async {
throw UnimplementedError('init() has not been implemented.');
}
Future<bool?> isPaused({required int cocosId}) async {
throw UnimplementedError('isPaused() has not been implemented.');
}
Future<bool?> isLoaded({required int cocosId}) async {
throw UnimplementedError('isLoaded() has not been implemented.');
}
Future<bool?> inBackground({required int cocosId}) async {
throw UnimplementedError('inBackground() has not been implemented.');
}
Future<bool?> createCocosPlayer({required int cocosId}) async {
throw UnimplementedError('createCocosPlayer() has not been implemented.');
}
Future<void> postMessage(
{required int cocosId,
required String gameObject,
required String methodName,
required String message}) {
throw UnimplementedError('postMessage() has not been implemented.');
}
Future<void> postJsonMessage(
{required int cocosId,
required String gameObject,
required String methodName,
required Map message}) {
throw UnimplementedError('postJsonMessage() has not been implemented.');
}
Future<void> pausePlayer({required int cocosId}) async {
throw UnimplementedError('pausePlayer() has not been implemented.');
}
Future<void> resumePlayer({required int cocosId}) async {
throw UnimplementedError('resumePlayer() has not been implemented.');
}
/// Opens cocos in it's own activity. Android only.
Future<void> openInNativeProcess({required int cocosId}) async {
throw UnimplementedError('openInNativeProcess() has not been implemented.');
}
Future<void> unloadPlayer({required int cocosId}) async {
throw UnimplementedError('unloadPlayer() has not been implemented.');
}
Future<void> quitPlayer({required int cocosId}) async {
throw UnimplementedError('quitPlayer() has not been implemented.');
}
Stream<CocosMessageEvent> onCocosMessage({required int cocosId}) {
throw UnimplementedError('onCocosMessage() has not been implemented.');
}
Stream<CocosLoadedEvent> onCocosUnloaded({required int cocosId}) {
throw UnimplementedError('onCocosUnloaded() has not been implemented.');
}
Stream<CocosCreatedEvent> onCocosCreated({required int cocosId}) {
throw UnimplementedError('onCocosUnloaded() has not been implemented.');
}
Stream<CocosSceneLoadedEvent> onCocosSceneLoaded({required int cocosId}) {
throw UnimplementedError('onCocosSceneLoaded() has not been implemented.');
}
/// Dispose of whatever resources the `cocosId` is holding on to.
void dispose({required int cocosId}) {
throw UnimplementedError('dispose() has not been implemented.');
}
/// Returns a widget displaying the cocos view
Widget buildView(
int creationId,
PlatformViewCreatedCallback onPlatformViewCreated, {
Map<String, dynamic> cocosOptions = const {},
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
bool? useAndroidViewSurf,
String? cocosSrcUrl,
}) {
throw UnimplementedError('buildView() has not been implemented.');
}
/// Returns a widget displaying the cocos view.
///
/// This method is similar to [buildView], but contains a parameter for
/// platforms that require a text direction.
///
/// Default behavior passes all parameters except `textDirection` to
/// [buildView]. This is for backward compatibility with existing
/// implementations. Platforms that use the text direction should override
/// this as the primary implementation, and delegate to it from buildView.
Widget buildViewWithTextDirection(
int creationId,
PlatformViewCreatedCallback onPlatformViewCreated, {
required TextDirection textDirection,
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
Map<String, dynamic> cocosOptions = const <String, dynamic>{},
bool? useAndroidViewSurf,
String? cocosSrcUrl,
}) {
return buildView(
creationId,
onPlatformViewCreated,
gestureRecognizers: gestureRecognizers,
cocosOptions: cocosOptions,
useAndroidViewSurf: useAndroidViewSurf,
cocosSrcUrl: cocosSrcUrl,
);
}
}

@ -0,0 +1,299 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:stream_transform/stream_transform.dart';
import '../helpers/events.dart';
import '../helpers/misc.dart';
import '../helpers/types.dart';
import 'cocos_widget_platform.dart';
import 'windows_cocos_widget_view.dart';
class MethodChannelCocosWidget extends CocosWidgetPlatform {
// Every method call passes the int cocosId
late final Map<int, MethodChannel> _channels = {};
/// Set [CocosWidgetFlutterPlatform] to use [AndroidViewSurface] to build the Google Maps widget.
///
/// This implementation uses hybrid composition to render the Cocos Widget
/// Widget on Android. This comes at the cost of some performance on Android
/// versions below 10. See
/// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more
/// information.
/// Defaults to false.
bool useAndroidViewSurface = true;
/// Accesses the MethodChannel associated to the passed cocosId.
MethodChannel channel(int cocosId) {
MethodChannel? channel = _channels[cocosId];
if (channel == null) {
throw UnknownCocosIDError(cocosId);
}
return channel;
}
MethodChannel ensureChannelInitialized(int cocosId) {
MethodChannel? channel = _channels[cocosId];
if (channel == null) {
channel = MethodChannel('plugin.xraph.com/cocos_view_$cocosId');
channel.setMethodCallHandler(
(MethodCall call) => _handleMethodCall(call, cocosId));
_channels[cocosId] = channel;
}
return channel;
}
/// Initializes the platform interface with [id].
///
/// This method is called when the plugin is first initialized.
@override
Future<void> init(int cocosId) {
MethodChannel channel = ensureChannelInitialized(cocosId);
return channel.invokeMethod<void>('cocos#waitForCocos');
}
/// Dispose of the native resources.
@override
Future<void> dispose({int? cocosId}) async {
try {
if (cocosId != null) await channel(cocosId).invokeMethod('cocos#dispose');
} catch (e) {
// ignore
}
}
// The controller we need to broadcast the different events coming
// from handleMethodCall.
//
// It is a `broadcast` because multiple controllers will connect to
// different stream views of this Controller.
final StreamController<CocosEvent> _cocosStreamController =
StreamController<CocosEvent>.broadcast();
// Returns a filtered view of the events in the _controller, by cocosId.
Stream<CocosEvent> _events(int cocosId) =>
_cocosStreamController.stream.where((event) => event.cocosId == cocosId);
Future<dynamic> _handleMethodCall(MethodCall call, int cocosId) async {
switch (call.method) {
case "events#onCocosMessage":
_cocosStreamController.add(CocosMessageEvent(cocosId, call.arguments));
break;
case "events#onCocosUnloaded":
_cocosStreamController.add(CocosLoadedEvent(cocosId, call.arguments));
break;
case "events#onCocosSceneLoaded":
_cocosStreamController.add(CocosSceneLoadedEvent(
cocosId, SceneLoaded.fromMap(call.arguments)));
break;
case "events#onCocosCreated":
_cocosStreamController.add(CocosCreatedEvent(cocosId, call.arguments));
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
}
@override
Future<bool?> isPaused({required int cocosId}) async {
return await channel(cocosId).invokeMethod('cocos#isPaused');
}
@override
Future<bool?> isReady({required int cocosId}) async {
return await channel(cocosId).invokeMethod('cocos#isReady');
}
@override
Future<bool?> isLoaded({required int cocosId}) async {
return await channel(cocosId).invokeMethod('cocos#isLoaded');
}
@override
Future<bool?> inBackground({required int cocosId}) async {
return await channel(cocosId).invokeMethod('cocos#inBackground');
}
@override
Future<bool?> createCocosPlayer({required int cocosId}) async {
return await channel(cocosId).invokeMethod('cocos#createPlayer');
}
@override
Stream<CocosMessageEvent> onCocosMessage({required int cocosId}) {
return _events(cocosId).whereType<CocosMessageEvent>();
}
@override
Stream<CocosLoadedEvent> onCocosUnloaded({required int cocosId}) {
return _events(cocosId).whereType<CocosLoadedEvent>();
}
@override
Stream<CocosCreatedEvent> onCocosCreated({required int cocosId}) {
return _events(cocosId).whereType<CocosCreatedEvent>();
}
@override
Stream<CocosSceneLoadedEvent> onCocosSceneLoaded({required int cocosId}) {
return _events(cocosId).whereType<CocosSceneLoadedEvent>();
}
@override
Widget buildViewWithTextDirection(
int creationId,
PlatformViewCreatedCallback onPlatformViewCreated, {
required TextDirection textDirection,
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
Map<String, dynamic> cocosOptions = const <String, dynamic>{},
bool? useAndroidViewSurf,
bool? height,
bool? width,
bool? cocosWebSource,
String? cocosSrcUrl,
}) {
final String _viewType = 'plugin.xraph.com/cocos_view';
if (useAndroidViewSurf != null) useAndroidViewSurface = useAndroidViewSurf;
final Map<String, dynamic> creationParams = cocosOptions;
if (defaultTargetPlatform == TargetPlatform.windows) {
return WindowsCocosWidgetView();
}
if (defaultTargetPlatform == TargetPlatform.android) {
if (!useAndroidViewSurface) {
return AndroidView(
viewType: _viewType,
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
layoutDirection: TextDirection.ltr,
);
}
return PlatformViewLink(
viewType: _viewType,
surfaceFactory: (
BuildContext context,
PlatformViewController controller,
) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: gestureRecognizers ??
const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (PlatformViewCreationParams params) {
final controller = PlatformViewsService.initExpensiveAndroidView(
id: params.id,
viewType: _viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onFocus: () => params.onFocusChanged(true),
);
controller
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..addOnPlatformViewCreatedListener(onPlatformViewCreated)
..create();
return controller;
},
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: _viewType,
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
return Text(
'$defaultTargetPlatform is not yet supported by the cocos player plugin');
}
@override
Widget buildView(
int creationId,
PlatformViewCreatedCallback onPlatformViewCreated, {
Map<String, dynamic> cocosOptions = const {},
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
bool? useAndroidViewSurf,
String? cocosSrcUrl,
}) {
return buildViewWithTextDirection(
creationId,
onPlatformViewCreated,
textDirection: TextDirection.ltr,
gestureRecognizers: gestureRecognizers,
cocosOptions: cocosOptions,
useAndroidViewSurf: useAndroidViewSurf,
cocosSrcUrl: cocosSrcUrl,
);
}
@override
Future<void> postMessage({
required int cocosId,
required String gameObject,
required String methodName,
required String message,
}) async {
await channel(cocosId).invokeMethod('cocos#postMessage', <String, dynamic>{
'gameObject': gameObject,
'methodName': methodName,
'message': message,
});
}
@override
Future<void> postJsonMessage({
required int cocosId,
required String gameObject,
required String methodName,
required Map message,
}) async {
await channel(cocosId).invokeMethod('cocos#postMessage', <String, dynamic>{
'gameObject': gameObject,
'methodName': methodName,
'message': json.encode(message),
});
}
@override
Future<void> pausePlayer({required int cocosId}) async {
await channel(cocosId).invokeMethod('cocos#pausePlayer');
}
@override
Future<void> resumePlayer({required int cocosId}) async {
await channel(cocosId).invokeMethod('cocos#resumePlayer');
}
@override
Future<void> openInNativeProcess({required int cocosId}) async {
await channel(cocosId).invokeMethod('cocos#openInNativeProcess');
}
@override
Future<void> unloadPlayer({required int cocosId}) async {
await channel(cocosId).invokeMethod('cocos#unloadPlayer');
}
@override
Future<void> quitPlayer({required int cocosId}) async {
await channel(cocosId).invokeMethod('cocos#quitPlayer');
}
}

@ -0,0 +1,4 @@
export '../facade_controller.dart';
export '../helpers/events.dart';
export '../helpers/misc.dart';
export '../helpers/types.dart';

@ -0,0 +1,213 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../facade_controller.dart';
import '../helpers/events.dart';
import 'device_method.dart';
import 'cocos_widget.dart';
import 'cocos_widget_platform.dart';
class MobileCocosWidgetController extends CocosWidgetController {
final MobileCocosWidgetState _cocosWidgetState;
/// The cocosId for this controller
final int cocosId;
/// used for cancel the subscription
StreamSubscription? _onCocosMessageSub,
_onCocosSceneLoadedSub,
_onCocosUnloadedSub;
MobileCocosWidgetController._(this._cocosWidgetState,
{required this.cocosId}) {
_connectStreams(cocosId);
}
/// Initialize [CocosWidgetController] with [id]
/// Mainly for internal use when instantiating a [CocosWidgetController] passed
/// in [CocosWidget.onCocosCreated] callback.
static Future<MobileCocosWidgetController> init(
int id, MobileCocosWidgetState cocosWidgetState) async {
await CocosWidgetPlatform.instance.init(id);
return MobileCocosWidgetController._(
cocosWidgetState,
cocosId: id,
);
}
@visibleForTesting
MethodChannel? get channel {
if (CocosWidgetPlatform.instance is MethodChannelCocosWidget) {
return (CocosWidgetPlatform.instance as MethodChannelCocosWidget)
.channel(cocosId);
}
return null;
}
void _connectStreams(int cocosId) {
if (_cocosWidgetState.widget.onCocosMessage != null) {
_onCocosMessageSub = CocosWidgetPlatform.instance
.onCocosMessage(cocosId: cocosId)
.listen((CocosMessageEvent e) =>
_cocosWidgetState.widget.onCocosMessage!(e.value));
}
if (_cocosWidgetState.widget.onCocosSceneLoaded != null) {
_onCocosSceneLoadedSub = CocosWidgetPlatform.instance
.onCocosSceneLoaded(cocosId: cocosId)
.listen((CocosSceneLoadedEvent e) =>
_cocosWidgetState.widget.onCocosSceneLoaded!(e.value));
}
if (_cocosWidgetState.widget.onCocosUnloaded != null) {
_onCocosUnloadedSub = CocosWidgetPlatform.instance
.onCocosUnloaded(cocosId: cocosId)
.listen((_) => _cocosWidgetState.widget.onCocosUnloaded!());
}
}
/// Checks to see if cocos player is ready to be used
/// Returns `true` if cocos player is ready.
Future<bool?>? isReady() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.isReady(cocosId: cocosId);
}
return null;
}
/// Get the current pause state of the cocos player
/// Returns `true` if cocos player is paused.
Future<bool?>? isPaused() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.isPaused(cocosId: cocosId);
}
return null;
}
/// Get the current load state of the cocos player
/// Returns `true` if cocos player is loaded.
Future<bool?>? isLoaded() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.isLoaded(cocosId: cocosId);
}
return null;
}
/// Helper method to know if Cocos has been put in background mode (WIP) unstable
/// Returns `true` if cocos player is in background.
Future<bool?>? inBackground() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.inBackground(cocosId: cocosId);
}
return null;
}
/// Creates a cocos player if it's not already created. Please only call this if cocos is not ready,
/// or is in unloaded state. Use [isLoaded] to check.
/// Returns `true` if cocos player was created succesfully.
Future<bool?>? create() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.createCocosPlayer(cocosId: cocosId);
}
return null;
}
/// Post message to cocos from flutter. This method takes in a string [message].
/// The [gameObject] must match the name of an actual cocos game object in a scene at runtime, and the [methodName],
/// must exist in a `MonoDevelop` `class` and also exposed as a method. [message] is an parameter taken by the method
///
/// ```dart
/// postMessage("GameManager", "openScene", "ThirdScene")
/// ```
Future<void>? postMessage(String gameObject, methodName, message) {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.postMessage(
cocosId: cocosId,
gameObject: gameObject,
methodName: methodName,
message: message,
);
}
return null;
}
/// Post message to cocos from flutter. This method takes in a Json or map structure as the [message].
/// The [gameObject] must match the name of an actual cocos game object in a scene at runtime, and the [methodName],
/// must exist in a `MonoDevelop` `class` and also exposed as a method. [message] is an parameter taken by the method
///
/// ```dart
/// postJsonMessage("GameManager", "openScene", {"buildIndex": 3, "name": "ThirdScene"})
/// ```
Future<void>? postJsonMessage(
String gameObject, String methodName, Map<String, dynamic> message) {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.postJsonMessage(
cocosId: cocosId,
gameObject: gameObject,
methodName: methodName,
message: message,
);
}
return null;
}
/// Pause the cocos in-game player with this method
Future<void>? pause() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.pausePlayer(cocosId: cocosId);
}
return null;
}
/// Resume the cocos in-game player with this method idf it is in a paused state
Future<void>? resume() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.resumePlayer(cocosId: cocosId);
}
return null;
}
/// Sometimes you want to open cocos in it's own process and openInNativeProcess does just that.
/// It works for Android and iOS is WIP
Future<void>? openInNativeProcess() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.openInNativeProcess(cocosId: cocosId);
}
return null;
}
/// Unloads cocos player from th current process (Works on Android only for now)
/// iOS is WIP. For more information please read [Cocos Docs](https://docs.cocos3d.com/2020.2/Documentation/Manual/CocosasaLibrary.html)
Future<void>? unload() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.unloadPlayer(cocosId: cocosId);
}
return null;
}
/// Quits cocos player. Note that this kills the current flutter process, thus quiting the app
Future<void>? quit() {
if (!_cocosWidgetState.widget.enablePlaceholder) {
return CocosWidgetPlatform.instance.quitPlayer(cocosId: cocosId);
}
return null;
}
/// cancel the subscriptions when dispose called
void _cancelSubscriptions() {
_onCocosMessageSub?.cancel();
_onCocosSceneLoadedSub?.cancel();
_onCocosUnloadedSub?.cancel();
_onCocosMessageSub = null;
_onCocosSceneLoadedSub = null;
_onCocosUnloadedSub = null;
}
void dispose() {
_cancelSubscriptions();
CocosWidgetPlatform.instance.dispose(cocosId: cocosId);
}
}

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
class WindowsCocosWidgetView extends StatefulWidget {
const WindowsCocosWidgetView({super.key});
@override
State<WindowsCocosWidgetView> createState() => _WindowsCocosWidgetViewState();
}
class _WindowsCocosWidgetViewState extends State<WindowsCocosWidgetView> {
@override
Widget build(BuildContext context) {
// TODO: Rex Update windows view
return const MouseRegion(
child: Texture(
textureId: 0,
),
);
}
}

@ -4,13 +4,14 @@ version: 0.0.1
homepage:
environment:
sdk: ^3.5.4
sdk: ^3.4.4
flutter: '>=3.3.0'
dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^2.0.2
stream_transform: ^2.0.0
dev_dependencies:
flutter_test:

@ -1,27 +0,0 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_cocos_view/flutter_cocos_view_method_channel.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
MethodChannelFlutterCocosView platform = MethodChannelFlutterCocosView();
const MethodChannel channel = MethodChannel('flutter_cocos_view');
setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
channel,
(MethodCall methodCall) async {
return '42';
},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
});
test('getPlatformVersion', () async {
expect(await platform.getPlatformVersion(), '42');
});
}

@ -1,29 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_cocos_view/flutter_cocos_view.dart';
import 'package:flutter_cocos_view/flutter_cocos_view_platform_interface.dart';
import 'package:flutter_cocos_view/flutter_cocos_view_method_channel.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockFlutterCocosViewPlatform
with MockPlatformInterfaceMixin
implements FlutterCocosViewPlatform {
@override
Future<String?> getPlatformVersion() => Future.value('42');
}
void main() {
final FlutterCocosViewPlatform initialPlatform = FlutterCocosViewPlatform.instance;
test('$MethodChannelFlutterCocosView is the default instance', () {
expect(initialPlatform, isInstanceOf<MethodChannelFlutterCocosView>());
});
test('getPlatformVersion', () async {
FlutterCocosView flutterCocosViewPlugin = FlutterCocosView();
MockFlutterCocosViewPlatform fakePlatform = MockFlutterCocosViewPlatform();
FlutterCocosViewPlatform.instance = fakePlatform;
expect(await flutterCocosViewPlugin.getPlatformVersion(), '42');
});
}
Loading…
Cancel
Save