Skip to main content

Overview

Nyuron is built for mobile devices, with careful attention to viewport configuration, touch input, and orientation handling. This guide covers the mobile-specific settings and patterns.

Project Configuration

project.godot Settings

Location: project.godot
[display]
window/size/viewport_width=270
window/size/viewport_height=480
window/stretch/mode="canvas_items"
window/stretch/scale_mode="integer"
window/handheld/orientation=6  # Sensor landscape

[rendering]
textures/canvas_textures/default_texture_filter=0  # Pixel art
renderer/rendering_method="mobile"
textures/vram_compression/import_etc2_astc=true
2d/snap/snap_2d_transforms_to_pixel=true

Key Settings Explained

Base resolution for portrait mode. This provides a good balance between performance and visual clarity on mobile devices.
  • Portrait games: 270x480 (default)
  • Landscape games: 480x270 (switched)
Scales the entire scene uniformly while maintaining aspect ratio. UI elements remain crisp on different screen sizes.
Uses integer scaling to preserve pixel-perfect rendering. Important for the pixel art style of Nyuron.
Allows the game to rotate based on device orientation. Individual scenes override this as needed.
Optimized renderer for mobile devices with lower power consumption.
Disables texture filtering for sharp pixel art. Essential for retro aesthetic.

Screen Orientation

Dynamic Orientation Switching

Nyuron switches orientation based on the minigame:
# Portrait mode (main menu, most games)
DisplayServer.screen_set_orientation(DisplayServer.SCREEN_PORTRAIT)
get_tree().root.set_content_scale_size(Vector2i(270, 480))

# Landscape mode (turtle_run, counting_animals)
DisplayServer.screen_set_orientation(DisplayServer.SCREEN_LANDSCAPE)
get_tree().root.set_content_scale_size(Vector2i(480, 270))

Orientation per Game

GameOrientationResolution
Main MenuPortrait270x480
food_catchPortrait270x480
worm_bucketPortrait270x480
memoricePortrait270x480
nyuron_colorPortrait270x480
turtle_runLandscape480x270
counting_animalsLandscape480x270

Setting Orientation in Your Game

When launching from main menu:
# In scripts/main_menu.gd
func _on_your_game_pressed():
	# Choose portrait or landscape
	DisplayServer.screen_set_orientation(DisplayServer.SCREEN_PORTRAIT)
	get_tree().root.set_content_scale_size(Vector2i(270, 480))
	get_tree().change_scene_to_file("res://minigames/your_game/scenes/Main.tscn")
When returning to menu:
# In your game's back button handler
func _on_back_pressed() -> void:
	# Always return to portrait
	DisplayServer.screen_set_orientation(DisplayServer.SCREEN_PORTRAIT)
	get_tree().root.set_content_scale_size(Vector2i(270, 480))
	get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
Examples:
  • Portrait: minigames/food_catch/main.gd:235-237
  • Landscape: scripts/main_menu.gd:204-207

Touch Input Handling

Touch Control Areas

Create transparent touch zones for mobile input:
# Create two ColorRect nodes in your scene
@onready var touch_left = $CanvasLayer/TouchLeft
@onready var touch_right = $CanvasLayer/TouchRight

func _ready():
	# Bind touch events
	touch_left.gui_input.connect(_on_touch_input.bind(-1))
	touch_right.gui_input.connect(_on_touch_input.bind(1))

func _on_touch_input(event: InputEvent, dir: float) -> void:
	if player == null:
		return
	
	if event is InputEventScreenTouch or event is InputEventMouseButton:
		if event.pressed:
			player.touch_dir = dir
		else:
			player.touch_dir = 0.0
Example: minigames/food_catch/main.gd:375-384

Setting Up Touch Zones

In your scene:
CanvasLayer
├── TouchLeft (ColorRect)
│   ├── anchor_left: 0
│   ├── anchor_right: 0.5
│   ├── anchor_top: 0
│   └── anchor_bottom: 1
└── TouchRight (ColorRect)
    ├── anchor_left: 0.5
    ├── anchor_right: 1
    ├── anchor_top: 0
    └── anchor_bottom: 1
Make them semi-transparent during development, fully transparent in production:
touch_left.color = Color(1, 0, 0, 0.1)  # Debug: light red
touch_right.color = Color(0, 0, 1, 0.1) # Debug: light blue
# Production:
touch_left.color = Color(0, 0, 0, 0)    # Invisible
touch_right.color = Color(0, 0, 0, 0)   # Invisible

Button-Based Input

For simpler games, use actual buttons:
@onready var left_btn = $CanvasLayer/LeftButton
@onready var right_btn = $CanvasLayer/RightButton

func _ready():
	left_btn.button_down.connect(_on_left_down)
	left_btn.button_up.connect(_on_left_up)
	right_btn.button_down.connect(_on_right_down)
	right_btn.button_up.connect(_on_right_up)

func _on_left_down():
	player.move_left()

func _on_left_up():
	player.stop()

Input Mapping

In project.godot, define input actions:
[input]
click={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,...)]
}
Use in code:
func _input(event):
	if event.is_action_pressed("click"):
		handle_click(event.position)

Responsive UI Scaling

CanvasLayer with Follow Viewport

Ensure UI stays in place across devices:
func _ready():
	var hud = $CanvasLayer
	hud.follow_viewport_enabled = true

Anchor-Based Positioning

Use anchors instead of fixed positions:
# Top-left corner
score_label.anchor_left = 0
score_label.anchor_top = 0

# Center
panel.anchor_left = 0.5
panel.anchor_top = 0.5
panel.grow_horizontal = Control.GROW_DIRECTION_BOTH
panel.grow_vertical = Control.GROW_DIRECTION_BOTH

# Bottom-right
button.anchor_left = 1
button.anchor_top = 1

Viewport Size Detection

Get current viewport size at runtime:
var screen_size: Vector2 = get_viewport_rect().size
print("Screen size: ", screen_size)  # 270x480 or 480x270
Use for dynamic positioning:
func _spawn_item():
	var spawn_x = randf_range(40.0, screen_size.x - 40.0)
	item.position = Vector2(spawn_x, -40.0)
Example: minigames/food_catch/main.gd:104, 146

Android Export Settings

export_presets.cfg

Location: export_presets.cfg
[preset.1]
name="Android"
platform="Android"
runnable=true

[preset.1.options]
gradle_build/use_gradle_build=true
architectures/arm64-v8a=true
architectures/armeabi-v7a=false
architectures/x86=false
architectures/x86_64=false

screen/immersive_mode=true
screen/edge_to_edge=true
screen/support_small=true
screen/support_normal=true
screen/support_large=true
screen/support_xlarge=true

Key Android Settings

  • arm64-v8a: Enabled (modern devices)
  • armeabi-v7a: Disabled (legacy devices)
This reduces APK size while supporting all modern Android devices.
Hides system UI (navigation bar, status bar) for fullscreen gameplay.
screen/immersive_mode=true
screen/edge_to_edge=true
Supports all screen sizes from small phones to tablets:
screen/support_small=true
screen/support_normal=true
screen/support_large=true
screen/support_xlarge=true

Performance Optimization

Mobile Rendering

Already configured in project.godot:
[rendering]
renderer/rendering_method="mobile"
textures/vram_compression/import_etc2_astc=true

Texture Compression

When importing textures, use ETC2 for Android:
  1. Select texture in FileSystem
  2. Go to Import tab
  3. Set:
    • Compress: VRAM Compressed
    • Format: ETC2_ASTC (for Android)

Pixel Snapping

For smooth pixel art movement:
2d/snap/snap_2d_transforms_to_pixel=true
In code:
player.position = player.position.round()  # Snap to nearest pixel

Testing on Mobile

1

Test touch controls

Verify touch areas respond correctly and cover appropriate screen regions
2

Test orientation changes

Ensure game handles orientation switches between scenes
3

Test on different screen sizes

Check UI remains visible and properly positioned on various devices
4

Test performance

Monitor FPS on target devices, aim for 60 FPS
5

Test battery usage

Ensure game doesn’t drain battery excessively

Common Mobile Issues

Problem: Touch input not detected.Solution: Ensure ColorRect/Control has mouse_filter set to MOUSE_FILTER_STOP and is above other UI elements in z-order.
Problem: Buttons or labels partially off-screen.Solution: Use anchors and margins instead of fixed positions. Test with safe area margins.
Problem: Game starts in wrong orientation.Solution: Set orientation in _ready() before gameplay starts:
func _ready():
    DisplayServer.screen_set_orientation(DisplayServer.SCREEN_PORTRAIT)
    get_tree().root.set_content_scale_size(Vector2i(270, 480))
Problem: Sprites look blurred.Solution: Set texture filter to Nearest in import settings and project settings:
textures/canvas_textures/default_texture_filter=0
Problem: FPS drops below 60.Solution:
  • Use object pooling for frequently spawned items
  • Limit particle effects
  • Reduce number of physics bodies
  • Use mobile renderer

Mobile-First Best Practices

Large touch targets

Make buttons at least 48x48 pixels for easy tapping

Clear visual feedback

Use animations and sounds to confirm touch input

Simple controls

Limit to 2-3 simultaneous touch points maximum

Test on real devices

Emulators don’t replicate true touch feel

Next Steps

UI Components

Build mobile-friendly UI elements

Creating a Minigame

Apply mobile patterns to new games

Build docs developers (and LLMs) love