← Volver al listado de tecnologías

Configuración del Entorno para iOS y Android

Por: Artiko
defoldiosandroidmobilesetupconfiguracion

Configuración del Entorno para iOS y Android

El desarrollo móvil con Defold requiere configuraciones específicas para cada plataforma. Esta guía te llevará paso a paso por la configuración completa del entorno para iOS y Android.

Prerrequisitos Generales

Herramientas Básicas

# Verificar instalación de Defold
defold --version

# Verificar Java Development Kit (Android)
java -version
javac -version

# Verificar Android SDK
adb version

Estructura del Proyecto Móvil

proyecto_movil/
├── game.project
├── main/
│   ├── main.collection
│   └── main.script
├── assets/
│   ├── textures/
│   │   ├── ui_atlas.atlas
│   │   └── game_atlas.atlas
│   └── sounds/
├── gui/
│   ├── main_menu.gui
│   └── game_hud.gui
├── extensions/
│   ├── ads/
│   ├── iap/
│   └── analytics/
└── bundles/
    ├── ios/
    └── android/

Configuración para Android

1. Instalación de Android SDK

Descarga e Instalación

# Descargar Android Command Line Tools
wget https://dl.google.com/android/repository/commandlinetools-linux-latest.zip

# Extraer en directorio específico
mkdir -p ~/Android/Sdk/cmdline-tools
unzip commandlinetools-linux-latest.zip -d ~/Android/Sdk/cmdline-tools
mv ~/Android/Sdk/cmdline-tools/cmdline-tools ~/Android/Sdk/cmdline-tools/latest

# Configurar variables de entorno
export ANDROID_HOME=~/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools

Instalación de Componentes

# Actualizar SDK Manager
sdkmanager --update

# Instalar plataformas Android
sdkmanager "platforms;android-34"
sdkmanager "platforms;android-33"
sdkmanager "platforms;android-32"

# Instalar build tools
sdkmanager "build-tools;34.0.0"
sdkmanager "build-tools;33.0.1"

# Instalar herramientas adicionales
sdkmanager "platform-tools"
sdkmanager "emulator"

# Verificar instalación
sdkmanager --list

2. Configuración del Proyecto Android

game.project - Configuración Android

[project]
title = Mi Juego Móvil
version = 1.0
dependencies =

[bootstrap]
main_collection = /main/main.collectionc

[render]
default_render = /builtins/render/default.renderc

[android]
version_code = 1
minimum_sdk_version = 21
target_sdk_version = 34
package = com.miestudio.mijuego
gcm_sender_id =
manifest = /bundles/android/AndroidManifest.xml
iap_provider = GooglePlay
input_method = HiddenInputField
immersive_mode = false
debuggable = false

[display]
width = 1080
height = 1920
high_dpi = true

[physics]
type = 2D
gravity_y = -1000
debug = false
debug_alpha = 0.9
world_count = 4
gravity_x = 0
gravity_z = 0
scale = 0.02
allow_dynamic_transforms = true
debug_scale = 30
max_collisions = 64
max_contacts = 128
contact_impulse_limit = 0
ray_cast_limit = 64
trigger_overlap_limit = 16

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="{{android.package}}"
    android:versionCode="{{android.version_code}}"
    android:versionName="{{project.version}}"
    android:installLocation="auto">

    <!-- Permisos básicos -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <!-- Permisos para almacenamiento -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <!-- Configuración de hardware -->
    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
    <uses-feature android:name="android.hardware.gamepad" android:required="false" />

    <!-- Soporte para pantallas -->
    <supports-screens
        android:xlargeScreens="true"
        android:largeScreens="true"
        android:normalScreens="true"
        android:smallScreens="true"
        android:anyDensity="true" />

    <application
        android:label="{{project.title}}"
        android:hasCode="true"
        android:debuggable="{{android.debuggable}}"
        android:icon="@mipmap/icon"
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">

        <activity
            android:name="com.dynamo.android.DefoldActivity"
            android:label="{{project.title}}"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:exported="true"
            android:screenOrientation="portrait"
            android:launchMode="singleTask">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- AdMob App ID -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>

    </application>
</manifest>

3. Script de Configuración Android

-- android_config.script
function init(self)
    -- Configurar orientación
    if defold then
        msg.post(".", "set_update_frequency", { frequency = 60 })
    end

    -- Configurar controles táctiles
    self.touch_enabled = true

    -- Configurar sistema de archivos
    self.save_path = sys.get_save_file("main", "save_data")

    print("Android configuration initialized")
end

function on_message(self, message_id, message, sender)
    if message_id == hash("window_callback") then
        if message.event == window.WINDOW_EVENT_FOCUS_LOST then
            -- Pausar el juego
            msg.post("main:/controller", "pause_game")
        elseif message.event == window.WINDOW_EVENT_FOCUS_GAINED then
            -- Reanudar el juego
            msg.post("main:/controller", "resume_game")
        end
    end
end

function on_input(self, action_id, action)
    if action_id == hash("touch") then
        if action.pressed then
            -- Manejar inicio de toque
            local touch_pos = vmath.vector3(action.x, action.y, 0)
            msg.post("main:/controller", "touch_start", { position = touch_pos })
        elseif action.released then
            -- Manejar fin de toque
            msg.post("main:/controller", "touch_end")
        end
    end
end

Configuración para iOS

1. Configuración del Entorno iOS

Xcode y Herramientas de Desarrollo

# Verificar instalación de Xcode
xcode-select --print-path

# Instalar herramientas de línea de comandos
xcode-select --install

# Verificar simuladores disponibles
xcrun simctl list devices

2. Configuración del Proyecto iOS

game.project - Configuración iOS

[ios]
app_icon_120x120 = /bundles/ios/AppIcon120x120.png
app_icon_180x180 = /bundles/ios/AppIcon180x180.png
app_icon_76x76 = /bundles/ios/AppIcon76x76.png
app_icon_152x152 = /bundles/ios/AppIcon152x152.png
app_icon_57x57 = /bundles/ios/AppIcon57x57.png
app_icon_114x114 = /bundles/ios/AppIcon114x114.png
app_icon_72x72 = /bundles/ios/AppIcon72x72.png
app_icon_144x144 = /bundles/ios/AppIcon144x144.png
launch_screen = /bundles/ios/LaunchScreen.storyboard
pre_renderered_icons = false
bundle_identifier = com.miestudio.mijuego
bundle_version = 1
infoplist = /bundles/ios/Info.plist
default_language = en
localizations = en,es

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>

    <key>CFBundleDisplayName</key>
    <string>{{project.title}}</string>

    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>

    <key>CFBundleIdentifier</key>
    <string>{{ios.bundle_identifier}}</string>

    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>

    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>

    <key>CFBundlePackageType</key>
    <string>APPL</string>

    <key>CFBundleShortVersionString</key>
    <string>{{project.version}}</string>

    <key>CFBundleVersion</key>
    <string>{{ios.bundle_version}}</string>

    <!-- Orientaciones soportadas -->
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
    </array>

    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>

    <!-- Configuración de pantalla -->
    <key>UIRequiresFullScreen</key>
    <true/>

    <key>UIStatusBarHidden</key>
    <true/>

    <!-- Game Center -->
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>gamekit</string>
    </array>

    <!-- AdMob App ID -->
    <key>GADApplicationIdentifier</key>
    <string>ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy</string>

    <!-- App Transport Security -->
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
</dict>
</plist>

3. LaunchScreen.storyboard

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <device id="retina6_12" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LaunchIcon" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
                                <rect key="frame" x="146.66666666666666" y="376" width="100" height="100"/>
                                <constraints>
                                    <constraint firstAttribute="width" constant="100" id="5cQ-KE-LBc"/>
                                    <constraint firstAttribute="height" constant="100" id="LfW-cd-eeE"/>
                                </constraints>
                            </imageView>
                        </subviews>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                        <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="YRO-k0-Ey4" levelPriority="1000" id="5cH-0I-8fX"/>
                            <constraint firstItem="YRO-k0-Ey4" centerY="6Tk-OE-BBY" id="Kzf-to-9mD"/>
                            <constraint firstItem="YRO-k0-Ey4" centerX="6Tk-OE-BBY" id="xeU-3F-WtF"/>
                        </constraints>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
    <resources>
        <image name="LaunchIcon" width="100" height="100"/>
    </resources>
</document>

Configuración Universal (iOS y Android)

1. Script de Detección de Plataforma

-- platform_detector.lua
local M = {}

function M.get_platform()
    local sys_info = sys.get_sys_info()
    return sys_info.system_name
end

function M.is_mobile()
    local platform = M.get_platform()
    return platform == "iPhone OS" or platform == "Android"
end

function M.is_ios()
    return M.get_platform() == "iPhone OS"
end

function M.is_android()
    return M.get_platform() == "Android"
end

function M.get_device_info()
    local sys_info = sys.get_sys_info()
    return {
        platform = sys_info.system_name,
        version = sys_info.system_version,
        language = sys_info.language,
        device_model = sys_info.device_model,
        manufacturer = sys_info.manufacturer
    }
end

return M

2. Configuración de Input Universal

-- mobile_input.script
local platform = require "main.platform_detector"

function init(self)
    -- Configurar input según la plataforma
    if platform.is_mobile() then
        msg.post(".", "acquire_input_focus")

        -- Configurar multitouch
        self.touches = {}
        self.max_touches = 10

        -- Configurar gestos
        self.gesture_threshold = 100
        self.swipe_threshold = 50

        print("Mobile input initialized for: " .. platform.get_platform())
    end
end

function on_input(self, action_id, action)
    if not platform.is_mobile() then
        return false
    end

    if action_id == hash("touch") then
        local touch_id = action.id or 1

        if action.pressed then
            -- Nuevo toque
            self.touches[touch_id] = {
                start_pos = vmath.vector3(action.x, action.y, 0),
                current_pos = vmath.vector3(action.x, action.y, 0),
                start_time = socket.gettime()
            }

            msg.post("main:/controller", "touch_pressed", {
                touch_id = touch_id,
                position = self.touches[touch_id].start_pos
            })

        elseif action.released then
            -- Fin del toque
            if self.touches[touch_id] then
                local touch_data = self.touches[touch_id]
                local duration = socket.gettime() - touch_data.start_time
                local distance = vmath.length(action.x - touch_data.start_pos.x, action.y - touch_data.start_pos.y)

                -- Detectar tipo de gesto
                if duration < 0.3 and distance < 30 then
                    -- Tap
                    msg.post("main:/controller", "tap", {
                        position = touch_data.start_pos
                    })
                elseif distance > self.swipe_threshold then
                    -- Swipe
                    local direction = vmath.normalize(vmath.vector3(
                        action.x - touch_data.start_pos.x,
                        action.y - touch_data.start_pos.y,
                        0
                    ))

                    msg.post("main:/controller", "swipe", {
                        direction = direction,
                        distance = distance
                    })
                end

                msg.post("main:/controller", "touch_released", {
                    touch_id = touch_id
                })

                self.touches[touch_id] = nil
            end

        else
            -- Movimiento del toque
            if self.touches[touch_id] then
                self.touches[touch_id].current_pos = vmath.vector3(action.x, action.y, 0)

                msg.post("main:/controller", "touch_moved", {
                    touch_id = touch_id,
                    position = self.touches[touch_id].current_pos
                })
            end
        end

        return true
    end

    return false
end

Build y Testing

1. Script de Build Automatizado

#!/bin/bash
# build_mobile.sh

PROJECT_PATH="."
BUILD_SERVER="https://build.defold.com"

echo "Building for mobile platforms..."

# Build para Android
echo "Building Android APK..."
curl -X POST \
    -H "Content-Type: application/json" \
    -d '{
        "platform": "android",
        "architectures": ["armv7-android", "arm64-android"]
    }' \
    "$BUILD_SERVER/build" > android_build.json

# Build para iOS
echo "Building iOS..."
curl -X POST \
    -H "Content-Type: application/json" \
    -d '{
        "platform": "ios",
        "architectures": ["arm64-ios", "x86_64-ios"]
    }' \
    "$BUILD_SERVER/build" > ios_build.json

echo "Mobile builds completed!"

2. Testing en Dispositivos

-- test_mobile.script
local platform = require "main.platform_detector"

function init(self)
    if platform.is_mobile() then
        -- Test de rendimiento
        self.fps_counter = 0
        self.fps_timer = 0

        -- Test de memoria
        self.memory_check_timer = 0

        -- Test de input
        self.touch_test_active = false

        print("Mobile testing initialized")
    end
end

function update(self, dt)
    if not platform.is_mobile() then
        return
    end

    -- Monitorear FPS
    self.fps_counter = self.fps_counter + 1
    self.fps_timer = self.fps_timer + dt

    if self.fps_timer >= 1.0 then
        local fps = self.fps_counter / self.fps_timer
        print("FPS: " .. math.floor(fps))

        if fps < 30 then
            print("WARNING: Low FPS detected!")
        end

        self.fps_counter = 0
        self.fps_timer = 0
    end

    -- Monitorear memoria cada 5 segundos
    self.memory_check_timer = self.memory_check_timer + dt
    if self.memory_check_timer >= 5.0 then
        local mem_info = profiler.get_memory_usage()
        print("Memory usage: " .. mem_info.total .. " bytes")
        self.memory_check_timer = 0
    end
end

Troubleshooting Común

Problemas de Android

  1. Error de SDK: Verificar variables de entorno ANDROID_HOME
  2. Certificados: Generar keystore para release builds
  3. Permisos: Verificar AndroidManifest.xml

Problemas de iOS

  1. Provisioning Profile: Configurar certificados de desarrollo
  2. Code Signing: Verificar identidad de firma
  3. Simulador: Usar simuladores de iOS para testing

Problemas Generales

  1. Rendimiento: Optimizar assets y texturas
  2. Compatibilidad: Probar en múltiples dispositivos
  3. Memoria: Monitorear uso de memoria constantemente

Este setup te proporciona una base sólida para el desarrollo móvil multiplataforma con Defold Engine.