# Godot Remote This is cross platform native module for [Godot Engine](https://github.com/godotengine/godot) v3 for control apps and games over WiFi or ADB. If you are developing on a non-touch device, this module is the best way to quickly test touch input or test mobile sensors data. [Video Demonstration](https://youtu.be/LbFcQnS3z3E) [Custom Packets Demo](https://youtu.be/RmhppDWZZk8) ## Support [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I53VZ2D) [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/dmitriysalnikov) ## Compiling the Module ### As a module 1. [configure environment](https://docs.godotengine.org/en/3.2/development/compiling/index.html) to build editor for your platform (you need to clone [3.2 branch](https://github.com/godotengine/godot/tree/3.2) not master) 2. copy ```godot_remote``` folder to the ```modules/``` directory or make [symlink](https://en.wikipedia.org/wiki/Symbolic_link) 3. compile engine with instructions from documentation above (e.g. ```scons p=windows tools=yes -j[place here count of your CPU threads]```) 4. run ```bin/godot[based on config]```. If everything compiles successfully, you'll find the new category in project settings ```Debug/Godot Remote``` where you can configure server. ![Settings](Images/Screenshots/settings.png) ### As a GDNative library 1. [Configure environment](https://docs.godotengine.org/en/3.2/development/compiling/index.html) to build editor for your platform 2. Generate api.json for GDNative api. ```bin/godot --gdnative-generate-json-api api.json``` 3. Copy api.json to the root directory of this repository 4. Compile godot-cpp (e.g. in godot-cpp directory run ```scons generate_bindings=true platform=windows target=release bits=64 -j8 ../api.json```) 5. Compile module for your platform (Available platforms: windows, osx, linux, ios, android. Tested platforms: windows, linux, android) 1. For android: Run in root directory ```[path to your android ndk root dir]/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk APP_PLATFORM=android-21``` 2. For all other platforms: ```scons platform=windows target=release -j8``` 6. Use produced library in ```bin/``` GDNative has limitations so here ```GodotRemote``` is not a singleton and you need to create autoload scene with attached NativeScript for ```GodotRemote``` class. Also there is no any settings in ```Debug/Godot Remote```. Enum constants in this version changed too (see [API Reference] ) **Currently, the GDNative version does not support the assignment of sensor data, so the editor will not support accelerometer, gyroscope, etc. Also, this version may crash at a random moment.** If GDNative becomes more stable, I will add the necessary code to easily integrate this module into any project, but now it just works.. sometimes. ### Additional parameters Also module has additional compilation parameters for scons script 1. ```godot_remote_no_default_resources``` (yes/no) default no - compile with or without default resources 2. ```godot_remote_disable_server``` (yes/no) default no - do not include server code 3. ```godot_remote_disable_client``` (yes/no) default no - do not include client code ## Download Precompiled binaries can be found on [GitHub Releases](https://github.com/DmitriySalnikov/GodotRemote/releases) page ### Mobile app On releases page you can found precompiled mobile app but also it can be downloaded from [Google Play](https://play.google.com/store/apps/details?id=com.dmitriysalnikov.godotremote) ## Configure Mobile App To open settings menu you need to touch the screen with 5 fingers at once. Then you'll see this settings menu: ![Settings](Images/Screenshots/mobile_settings.png) **Important:** after entering server address you should apply it by pressing `Set Type and Address` or `Set Type and Port` ## Custom client If need to support other platforms or you need a specific version of module integrated to the client app, you can build client from source code placed [here](godot_remote_client). If you don't want to use my client app you can check the [example client project](examples/simple_client) and build your own client. Or you can donate me some money with request to buy iPhone and adapt a client for it 🙂 ## API Reference Methods will be declared follows this template: ```python return_type function_name([arg_name1 : type [= defalut value]][, arg_name2 : type [= defalut value]]) ``` **Important:** All enums in GDNative version is exposed in GodotRemote class because of limitations. For example, if you want to use StreamState.STREAM_ACTIVE from GRClient you need to get property GRClient_STREAM_ACTIVE of GodotRemote __object__ ```python # Godot module: GRClient.STREAM_ACTIVE: # GDNative # *GodotRemote is autoload scene with attached NativeScript GodotRemote.GRClient_STREAM_ACTIVE ``` ### GodotRemote Main class of module. ```python # --- Properties # Canvas layer that shows notifications # type int, default 128 notifications_layer # Notifications position on screen # type GRNotifications.NotificationsPosition, default TC notifications_position # Is notifications enabled # type bool, default true notifications_enabled # Base duration for showing notifications # type float, default 3.0 notifications_duration # Notifcations style # type GRNotificationStyle notifications_style # --- Methods # Notifications # Adds or fully update existing notification # @title: Notification title # @text: Text of notification # @notification_icon: Notification icon from enum NotificationIcon # @update_existing: Updates existing notification # @duration_multiplier: Multiply base notifications duration void add_notification(title: String, text: String, notification_icon: GRNotifications.NotificationIcon = 0, update_existing: bool = true, duration_multiplier: float = 1.0) # Adds new notification or append text to existing notification # @title: Notification title # @text: Text of notification # @icon: Notification icon from enum NotificationIcon # @add_to_new_line: Adds text to new line or adds to current line void add_notification_or_append_string(title: String, text: String, icon: GRNotifications.NotificationIcon, add_to_new_line: bool = true, duration_multiplier: float = 1.0) # Adds notification or update one line of notification text # @title: Notification title # @id: Line ID # @text: Text of notification # @icon: Notification icon from enum NotificationIcon # @duration_multiplier: Multiply base notifications duration void add_notification_or_update_line(title: String, id: String, text: String, icon: GRNotifications.NotificationIcon, duration_multiplier: float = 1.0) # Clear all notifications void clear_notifications() # Get notifications list # @return list of all visible notifications Array get_all_notifications() # Get notification with specified title or null # @title: Notification title # @return matched notification GRNotificationPanel get_notification(title: String) # Get all notifications with specified title # @title: Notification title # @return list of visible notifications Array get_notifications_with_title(title: String) # Remove notifications with specified title # @title: Notifications title # @is_all_entries: Delete all notifications with @title if true void remove_notification(title: String, is_all_entries: bool = true) # Remove exact notification by reference # @notification: Notification reference void remove_notification_exact(notification: Node) # Client/Server # Create device: client or server # @device_type: Type of device # @return true if device created successful bool create_remote_device(device_type: GodotRemote.DeviceType = 0) # Start device # @return true if device valid bool start_remote_device() # Create and start device # @device_type: Type of device void create_and_start_device(device_type: GodotRemote.DeviceType = 0) # Remove and delete currently working device # @return true if succeed bool remove_remote_device() # Get device # @return client, server or null GRDevice get_device() # Utility functions # Not exposed to GDScript fuctions from Input class # And currently not available in GDNative void set_accelerometer(value: Vector3) void set_gravity(value: Vector3) void set_gyroscope(value: Vector3) void set_magnetometer(value: Vector3) # Set GodotRemote log level # @level: Level of logging void set_log_level(level: LogLevel) # Get GodotRemote module version # @return module version in format "MAJOR.MINOR.BUILD" String get_version() # --- Signals # Device added device_added() # Device removed device_removed() # --- Enumerations DeviceType: DEVICE_AUTO = 0 DEVICE_SERVER = 1 DEVICE_CLIENT = 2 LogLevel: LL_NONE = 4 LL_DEBUG = 0 LL_NORMAL = 1 LL_WARNING = 2 LL_ERROR = 3 ``` ### GRNotifications Container for all notifications ```python # --- Signals # Called when a single notification is added notification_added(title: String, text: String) # Called when a single notification is removed notification_removed(title: String, is_cleared: bool) # Called when all notifications are cleared notifications_cleared() # Called when notifications are enabled or disabled notifications_toggled(is_enabled: bool) # --- Enumerations NotificationIcon: ICON_NONE = 0 ICON_ERROR = 1 ICON_WARNING = 2 ICON_SUCCESS = 3 ICON_FAIL = 4 NotificationsPosition: TOP_LEFT = 0 TOP_CENTER = 1 TOP_RIGHT = 2 BOTTOM_LEFT = 3 BOTTOM_CENTER = 4 BOTTOM_RIGHT = 5 ``` ### GRNotificationStyle Helper class to store parameters of notifications style ```python # --- Properties # Style of background notifications panel # type StyleBox panel_style # Theme for notification close button # type Theme close_button_theme # Close button icon texture # type Texture close_button_icon # Notification title font # type Font title_font # Notification text font # type Font text_font # --- Methods # Get notification icon from this style # @notification_icon: Notfication icon id # @return icon texture of null Texture get_notification_icon(notification_icon: GRNotifications.NotificationIcon) # Set notification icon in this style # @notification_icon: Notfication icon id # @icon_texture: Icon texture void set_notification_icon(notification_icon: GRNotifications.NotificationIcon, icon_texture: Texture) ``` ### GRInputData Container for all InputEvents ```python # --- Enumerations InputType: _NoneIT = 0 _InputDeviceSensors = 1 _InputEvent = 64 _InputEventAction = 65 _InputEventGesture = 66 _InputEventJoypadButton = 67 _InputEventJoypadMotion = 68 _InputEventKey = 69 _InputEventMagnifyGesture = 70 _InputEventMIDI = 71 _InputEventMouse = 72 _InputEventMouseButton = 73 _InputEventMouseMotion = 74 _InputEventPanGesture = 75 _InputEventScreenDrag = 76 _InputEventScreenTouch = 77 _InputEventWithModifiers = 78 _InputEventMAX = 79 ``` ### GRPacket The basic data type used to exchange information between the client and the server ```python # --- Enumerations PacketType: NonePacket = 0 SyncTime = 1 ImageData = 2 InputData = 3 ServerSettings = 4 MouseModeSync = 5 CustomInputScene = 6 ClientStreamOrientation = 7 ClientStreamAspect = 8 CustomUserData = 9 Ping = 128 Pong = 192 ``` ### GRDevice Base class for client and server ```python # --- Properties # Connection port # type int, default 52341 port # --- Methods # Send user data to remote device # @packet_id: any data to identify your packet # @user_data: any data to send to remote device # @full_objects: flag for full serialization of objects, possibly with their executable code. For more info check Godot's PacketPeer.put_var() and PacketPeer.get_var() void send_user_data(packet_id: Variant, user_data: Variant, full_objects: bool = false) # Get average FPS # @return average FPS float get_avg_fps() # Get minimum FPS # @return minimum FPS float get_min_fps() # Get maximum FPS # @return maximum FPS float get_max_fps() # Get average ping # @return average ping float get_avg_ping() # Get minimum ping # @return minimum ping float get_min_ping() # Get maximum ping # @return maximum ping float get_max_ping() # Get device status WorkingStatus get_status() # Start device void start() # Stop device void stop() # --- Signals # Device status changed status_changed(status: GRDevice.WorkingStatus) # User data received from a remote device user_data_received(packet_id: Variant, user_data: Variant) # --- Enumerations ImageCompressionType: COMPRESSION_UNCOMPRESSED = 0 COMPRESSION_JPG = 1 COMPRESSION_PNG = 2 Subsampling: SUBSAMPLING_Y_ONLY = 0 SUBSAMPLING_H1V1 = 1 SUBSAMPLING_H2V1 = 2 SUBSAMPLING_H2V2 = 3 TypesOfServerSettings: SERVER_SETTINGS_USE_INTERNAL = 0 SERVER_SETTINGS_VIDEO_STREAM_ENABLED = 1 SERVER_SETTINGS_COMPRESSION_TYPE = 2 SERVER_SETTINGS_JPG_QUALITY = 3 SERVER_SETTINGS_SKIP_FRAMES = 4 SERVER_SETTINGS_RENDER_SCALE = 5 WorkingStatus: STATUS_STARTING = 3 STATUS_STOPPING = 2 STATUS_WORKING = 1 STATUS_STOPPED = 0 ``` ### GRServer ```python # --- Properties # Server password # type String, default "" password # Path to the custom input scene. # type String, default "" custom_input_scene # Is custom input scene compressed ## Doesn't work in GDNative # type bool, default true custom_input_scene_compressed # Compression type of custom input scene ## Doesn't work in GDNative # type File.CompressionMode, default FastLZ custom_input_scene_compression_type # --- Methods # Set whether the stream is enabled bool set_video_stream_enabled(value : bool) # Get whether the stream is enabled bool is_video_stream_enabled() # Set how many frames to skip bool set_skip_frames(frames : int) # Get the number of skipping frames int get_skip_frames() # Set JPG quality bool set_jpg_quality(quality : int) # Get JPG quality int get_jpg_quality() # Set the scale of the stream bool set_render_scale(scale : float) # Get stream scale float get_render_scale() # Force update custom input scene on client void force_update_custom_input_scene() # Get resize viewport node # @return resize viewport or null GRSViewport get_gr_viewport() # --- Signals # On client connected client_connected(device_id: String) # On client disconnected client_disconnected(device_id: String) # On orientation of client's screen or viewport changed client_viewport_orientation_changed(is_vertical: bool) # On client's screen or viewport aspect ratio changed client_viewport_aspect_ratio_changed(stream_aspect: float) ``` ### GRClient ```python # --- Properties # Capture input only when containing control has focus # type bool, default false capture_on_focus # Capture input only when stream image hovered # type bool, default true capture_when_hover # Capture mouse pointer and touch events # type bool, default true capture_pointer # Capture input # type bool, default true capture_input # Type of connection # type GRClient.ConnectionType, default CONNECTION_WiFi connection_type # Frequency of sending data to the server # type int, default 60 target_send_fps # Stretch mode of stream image # type GRClient.StretchMode, default STRETCH_KEEP_ASPECT stretch_mode # Use texture filtering of stream image # type bool, default true texture_filtering # Password # type String, default "" password # ID of device # type String, default 6 random digits and characters device_id # Sync viewport orientation with server # type bool, default true viewport_orientation_syncing # Sync viewport aspect ratio with server # type bool, default true viewport_aspect_ratio_syncing # Receive updated server settings # type bool, default false server_settings_syncing # --- Methods # Restore settings on server void disable_overriding_server_settings() # Get the current visible custom input scene # @return: Custom input scene Node get_custom_input_scene() # Get server address # @return server address String get_address() # Is connected to server # @return true if connected to server bool is_connected_to_host() # Is stream active # @return true if stream active bool is_stream_active() # Set server address to connect # @ip: IP of server # @return true if address is valid bool set_address(ip: String) # Set both server address and port # @ip: IP of server # @port: Port of server # @return true if address is valid bool set_address_port(ip: String, port: int) # Set the control to show stream in # @control_node: Control where stream will be shown # @position_in_node: Position of stream in parent void set_control_to_show_in(control_node: Control, position_in_node: int = 0) # Set custom material for no signal screen # @material: Custom material void set_custom_no_signal_material(material: Material) # Set custom horizontal texture for no signal screen # @texture: Custom texture void set_custom_no_signal_texture(texture: Texture) # Set custom vertical texture for no signal screen # @texture: Custom texture void set_custom_no_signal_vertical_texture(texture: Texture) # Override setting on server # @setting: Which setting need to change # @value: Value of setting void set_server_setting(setting: GRdevice.TypesOfServerSettings, value: Variant) # --- Signals # On custom input scene added and becomes visible custom_input_scene_added() # On custom input scene removed custom_input_scene_removed() # On connection state changed connection_state_changed(is_connected: bool) # On stream state changed stream_state_changed(state: GRClient.StreamState) # On mouse mode changed on server mouse_mode_changed(mouse_mode: Input.MouseMode) # On received server settings from server server_settings_received(settings: Dictionary) # --- Enumerations ConnectionType: CONNECTION_ADB = 1 CONNECTION_WiFi = 0 StreamState: STREAM_NO_SIGNAL = 0 STREAM_ACTIVE = 1 STREAM_NO_IMAGE = 2 StretchMode: STRETCH_KEEP_ASPECT = 0 STRETCH_FILL = 1 ``` There is no need to describe other classes here ## Custom Input Scenes In custom input scenes you can use everything you want but to send InputEvent's from client to server you must emulate input. Or use the send_user_data() method and user_data_received signal for send and receive custom packets. Example: ```python # -- With InputEvent's func _on_pressed(): # Create event for pressed state var iea_p = InputEventAction.new() iea_p.pressed = true iea_p.action = "jump" # Create event for released state var iea_r = InputEventAction.new() iea_r.pressed = false iea_p.action = "jump" # Parse event to send it to the server Input.parse_input_event(iea_p) Input.parse_input_event(iea_r) # -- With custom packets # on first device func _ready(): GodotRemote.get_device().connect("user_data_received", self, "_on_user_data_received") func _on_user_data_received(id, data): print("Received packet: %s, data: %s" % [id, data]) # on second device func _on_button_pressed(): GodotRemote.get_device().send_user_data("bg_color", color, false) ``` ## License MIT license