Commit 4dfa63fa authored by Gabriel Le Breton's avatar Gabriel Le Breton

Merge branch 'feature/android-exemple' into 'master'

Feature/android example

See merge request !23
parents 5292273c 9d79dbda
Pipeline #82313712 passed with stages
in 39 minutes and 40 seconds
......@@ -26,6 +26,14 @@ get-license:
<<: *cache
script:
- chmod +x ./ci/get_license.sh && ./ci/get_license.sh
get-android-license:
when: manual
stage: build_and_test
image: gableroux/unity3d:2019.1.14f1-android
<<: *cache
script:
- chmod +x ./ci/get_license.sh && ./ci/get_license.sh
.test: &test
stage: build_and_test
......@@ -80,6 +88,12 @@ build-WebGL:
<<: *build
variables:
BUILD_TARGET: WebGL
build-android:
<<: *build
image: gableroux/unity3d:2019.1.14f1-android
variables:
BUILD_TARGET: Android
pages:
image: alpine:latest
......
using UnityEngine;
public abstract class Controller : MonoBehaviour
{
public abstract bool GetInputDown(InputButton input);
public abstract bool GetInputUp(InputButton input);
public abstract bool GetInput(InputButton input);
}
fileFormatVersion: 2
guid: 52b6279d67b141c4eb10e3d74ac61758
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
public enum InputButton
{
Left,
Right,
Down,
Up,
Action
}
fileFormatVersion: 2
guid: 9b40809f46e63834b817bae7f2e561b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
......@@ -10,8 +10,13 @@ namespace RPGM.UI
public class InputController : MonoBehaviour
{
public float stepSize = 0.1f;
public Controller mobileController;
public Controller standaloneController;
public Canvas mobileCanvas;
GameModel model = Schedule.GetModel<GameModel>();
private Controller controller;
public enum State
{
CharacterControl,
......@@ -19,6 +24,16 @@ namespace RPGM.UI
Pause
}
public void Awake()
{
#if UNITY_ANDROID || UNITY_IOS
controller = mobileController;
#else
controller = standaloneController;
mobileCanvas.enabled = false;
#endif
}
State state;
public void ChangeState(State state) => this.state = state;
......@@ -39,23 +54,23 @@ namespace RPGM.UI
void DialogControl()
{
model.player.nextMoveCommand = Vector3.zero;
if (Input.GetKeyDown(KeyCode.LeftArrow))
if (controller.GetInputDown(InputButton.Left))
model.dialog.FocusButton(-1);
else if (Input.GetKeyDown(KeyCode.RightArrow))
else if (controller.GetInputDown(InputButton.Right))
model.dialog.FocusButton(+1);
if (Input.GetKeyDown(KeyCode.Space))
if (controller.GetInputDown(InputButton.Action))
model.dialog.SelectActiveButton();
}
void CharacterControl()
{
if (Input.GetKey(KeyCode.UpArrow))
if (controller.GetInput(InputButton.Up))
model.player.nextMoveCommand = Vector3.up * stepSize;
else if (Input.GetKey(KeyCode.DownArrow))
else if (controller.GetInput(InputButton.Down))
model.player.nextMoveCommand = Vector3.down * stepSize;
else if (Input.GetKey(KeyCode.LeftArrow))
else if (controller.GetInput(InputButton.Left))
model.player.nextMoveCommand = Vector3.left * stepSize;
else if (Input.GetKey(KeyCode.RightArrow))
else if (controller.GetInput(InputButton.Right))
model.player.nextMoveCommand = Vector3.right * stepSize;
else
model.player.nextMoveCommand = Vector3.zero;
......
using System;
using System.Collections.Generic;
using UnityEngine;
public class MobileController : Controller, ISerializationCallbackReceiver
{
[SerializeField] private List<InputButtonState> allInputKeys = new List<InputButtonState>();
private Dictionary<InputButton, ButtonState> inputsToButtonStates = new Dictionary<InputButton, ButtonState>();
public override bool GetInput(InputButton input)
{
var button = GetKeyCodeFromInputButton(input);
return button ? button.Press : false;
}
public override bool GetInputDown(InputButton input)
{
var button = GetKeyCodeFromInputButton(input);
return button ? button.PressDown : false;
}
public override bool GetInputUp(InputButton input)
{
var button = GetKeyCodeFromInputButton(input);
return button ? button.PressUp : false;
}
private ButtonState GetKeyCodeFromInputButton(InputButton input)
{
if (inputsToButtonStates.TryGetValue(input, out ButtonState keyCode)) {
return keyCode;
}
return null;
}
public void OnAfterDeserialize()
{
inputsToButtonStates.Clear();
InputButtonState inputButtonState;
for (int i = allInputKeys.Count - 1; i > -1; i--) {
inputButtonState = allInputKeys[i];
inputsToButtonStates.Add(inputButtonState.inputButton, inputButtonState.buttonState);
}
}
public void OnBeforeSerialize()
{
InputButton input;
foreach (var enumObj in Enum.GetValues(typeof(InputButton))) {
input = (InputButton) enumObj;
if (allInputKeys.Find(inputButtonState => inputButtonState.inputButton == input) == null) {
allInputKeys.Add(new InputButtonState() {
inputButton = input
});
}
}
}
[Serializable]
public class InputButtonState
{
public InputButton inputButton;
public ButtonState buttonState;
}
}
fileFormatVersion: 2
guid: 9c69b3898cc3d3649aadb0e7a6e5efab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System;
using System.Collections.Generic;
using UnityEngine;
public class StandaloneController : Controller, ISerializationCallbackReceiver
{
[SerializeField] private List<InputKey> allInputKeys = new List<InputKey>();
private Dictionary<InputButton, KeyCode> inputsToKeycodes = new Dictionary<InputButton, KeyCode>();
public override bool GetInput(InputButton input)
{
return Input.GetKey(GetKeyCodeFromInputButton(input));
}
public override bool GetInputDown(InputButton input)
{
return Input.GetKeyDown(GetKeyCodeFromInputButton(input));
}
public override bool GetInputUp(InputButton input)
{
return Input.GetKeyUp(GetKeyCodeFromInputButton(input));
}
private KeyCode GetKeyCodeFromInputButton(InputButton input)
{
if (inputsToKeycodes.TryGetValue(input, out KeyCode keyCode)) {
return keyCode;
}
return KeyCode.None;
}
public void OnAfterDeserialize()
{
inputsToKeycodes.Clear();
InputKey inputKey;
for (int i = allInputKeys.Count - 1; i > -1; i--) {
inputKey = allInputKeys[i];
inputsToKeycodes.Add(inputKey.inputButton, inputKey.keyCode);
}
}
public void OnBeforeSerialize()
{
InputButton input;
foreach (var enumObj in Enum.GetValues(typeof(InputButton))) {
input = (InputButton) enumObj;
if (allInputKeys.Find(inputKey => inputKey.inputButton == input) == null) {
allInputKeys.Add(new InputKey() {
inputButton = input
});
}
}
}
[Serializable]
public class InputKey
{
public InputButton inputButton;
public KeyCode keyCode;
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: aa870176ef306544084ce058d360bf2f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using UnityEngine.UI;
public class ButtonState : Button
{
private bool pressUp;
private bool pressDown;
private bool press;
public bool PressDown => pressDown;
public bool PressUp => pressUp;
public bool Press => press;
private void Update()
{
bool newPress = IsPressed();
pressUp = !newPress && press;
pressDown = newPress && !press;
press = newPress;
}
}
fileFormatVersion: 2
guid: 3c09e1556b71ad14ea5def6cffd42dd5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using UnityEditor;
using System.Linq;
using System;
using System.IO;
static class BuildCommand
{
private const string KEYSTORE_PASS = "KEYSTORE_PASS";
private const string KEY_ALIAS_PASS = "KEY_ALIAS_PASS";
private const string KEY_ALIAS_NAME = "KEY_ALIAS_NAME";
private const string KEYSTORE = "keystore.keystore";
private const string BUILD_OPTIONS_ENV_VAR = "BuildOptions";
static string GetArgument(string name)
{
string[] args = Environment.GetCommandLineArgs();
......@@ -42,7 +49,12 @@ static class BuildCommand
#endif
}
return ToEnum<BuildTarget>(buildTargetName, BuildTarget.NoTarget);
if (buildTargetName.TryConvertToEnum(out BuildTarget target))
return target;
Console.WriteLine($":: {nameof(buildTargetName)} \"{buildTargetName}\" not defined on enum {nameof(BuildTarget)}, using {nameof(BuildTarget.NoTarget)} enum to build");
return BuildTarget.NoTarget;
}
static string GetBuildPath()
......@@ -67,70 +79,115 @@ static class BuildCommand
return buildName;
}
static string GetFixedBuildPath(BuildTarget buildTarget, string buildPath, string buildName)
static string GetFixedBuildPath(BuildTarget buildTarget, string buildPath, string buildName, BuildOptions buildOptions)
{
if (buildTarget.ToString().ToLower().Contains("windows"))
{
buildName = buildName + ".exe";
if (buildTarget.ToString().ToLower().Contains("windows")) {
buildName += ".exe";
} else if (buildTarget == BuildTarget.Android && buildOptions == BuildOptions.None) {
buildName += ".apk";
}
return buildPath + buildName;
}
static BuildOptions GetBuildOptions()
{
string buildOptions = GetArgument("customBuildOptions");
return buildOptions == "AcceptExternalModificationsToPlayer" ? BuildOptions.AcceptExternalModificationsToPlayer : BuildOptions.None;
if (TryGetEnv(BUILD_OPTIONS_ENV_VAR, out string envVar)) {
string[] allOptionVars = envVar.Split(',');
BuildOptions allOptions = BuildOptions.None;
BuildOptions option;
string optionVar;
int length = allOptionVars.Length;
Console.WriteLine($":: Detecting {BUILD_OPTIONS_ENV_VAR} env var with {length} elements ({envVar})");
for (int i = 0; i < length; i++) {
optionVar = allOptionVars[i];
if (optionVar.TryConvertToEnum(out option)) {
allOptions |= option;
}
else {
Console.WriteLine($":: Cannot convert {optionVar} to {nameof(BuildOptions)} enum, skipping it.");
}
}
return allOptions;
}
return BuildOptions.None;
}
// https://stackoverflow.com/questions/1082532/how-to-tryparse-for-enum-value
static TEnum ToEnum<TEnum>(this string strEnumValue, TEnum defaultValue)
static bool TryConvertToEnum<TEnum>(this string strEnumValue, out TEnum value)
{
if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
{
return defaultValue;
value = default;
return false;
}
return (TEnum)Enum.Parse(typeof(TEnum), strEnumValue);
value = (TEnum)Enum.Parse(typeof(TEnum), strEnumValue);
return true;
}
static string getEnv(string key, bool secret = false, bool verbose = true)
static bool TryGetEnv(string key, out string value)
{
var env_var = Environment.GetEnvironmentVariable(key);
if (verbose)
{
if (env_var != null)
{
if (secret)
{
Console.WriteLine(":: env['" + key + "'] set");
}
else
{
Console.WriteLine(":: env['" + key + "'] set to '" + env_var + "'");
}
}
else
{
Console.WriteLine(":: env['" + key + "'] is null");
}
}
return env_var;
value = Environment.GetEnvironmentVariable(key);
return !string.IsNullOrEmpty(value);
}
static void PerformBuild()
{
Console.WriteLine(":: Performing build");
//PlayerSettings.keystorePass = getEnv ("KEYSTORE_PASS", true);
//PlayerSettings.keyaliasPass = getEnv ("KEY_ALIAS_PASS", true);
//EditorSetup.AndroidSdkRoot = getEnv ("ANDROID_SDK_HOME");
//EditorSetup.JdkRoot = getEnv ("JAVA_HOME");
//EditorSetup.AndroidNdkRoot = getEnv ("ANDROID_NDK_HOME");
var buildTarget = GetBuildTarget();
var buildPath = GetBuildPath();
var buildName = GetBuildName();
var fixedBuildPath = GetFixedBuildPath(buildTarget, buildPath, buildName);
BuildPipeline.BuildPlayer(GetEnabledScenes(), fixedBuildPath, buildTarget, GetBuildOptions());
if (buildTarget == BuildTarget.Android) {
HandleAndroidKeystore();
}
var buildPath = GetBuildPath();
var buildName = GetBuildName();
var buildOptions = GetBuildOptions();
var fixedBuildPath = GetFixedBuildPath(buildTarget, buildPath, buildName, buildOptions);
BuildPipeline.BuildPlayer(GetEnabledScenes(), fixedBuildPath, buildTarget, buildOptions);
Console.WriteLine(":: Done with build");
}
private static void HandleAndroidKeystore()
{
PlayerSettings.Android.useCustomKeystore = false;
if (!File.Exists(KEYSTORE)) {
Console.WriteLine($":: {KEYSTORE} not found, skipping setup, using Unity's default keystore");
return;
}
PlayerSettings.Android.keystoreName = KEYSTORE;
string keystorePass;
string keystoreAliasPass;
if (TryGetEnv(KEY_ALIAS_NAME, out string keyaliasName)) {
PlayerSettings.Android.keyaliasName = keyaliasName;
Console.WriteLine($":: using ${KEY_ALIAS_NAME} env var on PlayerSettings");
} else {
Console.WriteLine($":: ${KEY_ALIAS_NAME} env var not set, using Project's PlayerSettings");
}
if (!TryGetEnv(KEYSTORE_PASS, out keystorePass)) {
Console.WriteLine($":: ${KEYSTORE_PASS} env var not set, skipping setup, using Unity's default keystore");
return;
}
if (!TryGetEnv(KEY_ALIAS_PASS, out keystoreAliasPass)) {
Console.WriteLine($":: ${KEY_ALIAS_PASS} env var not set, skipping setup, using Unity's default keystore");
return;
}
PlayerSettings.Android.useCustomKeystore = true;
PlayerSettings.Android.keystorePass = keystorePass;
PlayerSettings.Android.keyaliasPass = keystoreAliasPass;
}
}
......@@ -13,7 +13,7 @@ PlayerSettings:
useOnDemandResources: 0
accelerometerFrequency: 60
companyName: DefaultCompany
productName: ProductName
productName: DefaultProductName
defaultCursor: {fileID: 0}
cursorHotspot: {x: 0, y: 0}
m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
......@@ -159,7 +159,8 @@ PlayerSettings:
resolutionScalingMode: 0
androidSupportedAspectRatio: 1
androidMaxAspectRatio: 2.1
applicationIdentifier: {}
applicationIdentifier:
Android: com.DefaultCompany.DefaultProductName
buildNumber: {}
AndroidBundleVersionCode: 1
AndroidMinSdkVersion: 16
......@@ -270,7 +271,99 @@ PlayerSettings:
androidGamepadSupportLevel: 0
resolutionDialogBanner: {fileID: 0}
m_BuildTargetIcons: []
m_BuildTargetPlatformIcons: []
m_BuildTargetPlatformIcons:
- m_BuildTarget: Android
m_Icons:
- m_Textures: []
m_Width: 432
m_Height: 432
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 324
m_Height: 324
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 216
m_Height: 216
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 162
m_Height: 162
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 108
m_Height: 108
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 81
m_Height: 81
m_Kind: 2
m_SubKind:
- m_Textures: []
m_Width: 192
m_Height: 192
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 144
m_Height: 144
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 96
m_Height: 96
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 72
m_Height: 72
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 48
m_Height: 48
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 36
m_Height: 36
m_Kind: 1
m_SubKind:
- m_Textures: []
m_Width: 192
m_Height: 192
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 144
m_Height: 144
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 96
m_Height: 96
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 72
m_Height: 72
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 48
m_Height: 48
m_Kind: 0
m_SubKind:
- m_Textures: []
m_Width: 36
m_Height: 36
m_Kind: 0
m_SubKind:
m_BuildTargetBatching: []
m_BuildTargetGraphicsAPIs: []
m_BuildTargetVRSettings: []
......
......@@ -4,7 +4,7 @@
UnityConnectSettings:
m_ObjectHideFlags: 0
serializedVersion: 1
m_Enabled: 0
m_Enabled: 1
m_TestMode: 0
m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events
m_EventUrl: https://cdp.cloud.unity3d.com/v1/events
......