标签归档:Unity

Unity与Android交互(三):Android App调用Unity打包的AAR文件

开发环境:Android Studio 2.3.3 , Unity 2017.1.2

我们打开在第一篇文章中建立的 Android App 项目,新建一个模块,并导入第二篇文章中打包的 AAR 文件:File > New Module > Import .JAR/.ARR Package ,选择第二篇文章中打包的 testunity-debug.aar 文件。

之后右键点击 Project 面板中的 app 文件夹,选择 Open Module Settings 的 Dependencies 选项卡,添加 :testunity-debug 模块,如果会发生错误,我们先确保修改 build.gradle (Module: app) 文件中的 minSdkVersion 为与 Unity 中一致的 API 版本。

添加Unity的AAR后报错

添加Unity的AAR后报错

修改minSdkVersion与Unity中一致

修改minSdkVersion与Unity中一致

API 版本修改完成后,这里还有一个 Icon 和 Theme 相关的报错:

Icon和Theme的报错

Icon和Theme的报错

我们打开 App 的 AndroidManifest.xml 文件,告诉 merge 合并程序使用哪一个 Icon 和 Theme ,分别在  manifest 标签和 application 标签下添加代码:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.test.androidapp">
    <application
        tools:replace="android:icon,android:theme"
        ...
        android:theme="@style/AppTheme">
        ...
    </application>
</manifest>
manifest和application标签下添加代码

manifest和application标签下添加代码

保存,点击: Sync Project with Gradle Files ,Gradle 会对当前程序进行编译,这个时候就会成功编译了。

接下来我们像第一篇文章一样,开始调用 Unity 的 Activity ,我们在 MainActicity 中先引用导出的 AAR 包的 Unity 的 Activity:

import com.test.testunity.UnityPlayerActivity;

在 Button 的 click 事件中调用显示 Unity 的 Activity :

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this, UnityPlayerActivity.class);
        startActivity(intent);
    }
});

到这里,我们就完成了在 Android App 中调用并显示 Unity 程序的所有步骤。

注意:我们这里并没有在 Unity 中添加返回键事件,所以你点击手机的返回键时不会有任何反应,如果你使用 Application.Quit(); 你会发现会把整个 Android App 也关掉,这一部分的处理可以参考网上的其他文章。

如果在文中发现了 BUG ,或发现某些地方写的不够清楚,欢迎在评论中告诉我,我会完善。

项目Git地址:https://gitee.com/trlanfeng/UnityAndroid/

Unity与Android交互(二):Unity项目打包为AAR供Android App调用

开发环境:Android Studio 2.3.3 , Unity 2017.1.2

这一篇我们讲如何将 Unity 程序打包为 AAR 文件,使 Android App 可以调用。

在 Unity 5.x 时, Unity 的 Build System 中就加入了使用 Gradle 方式输出项目。

Unity中使用Gradle输出

Unity中使用Gradle输出

当使用 Gradle 方式时,就只有 Export 按钮了,而不是 Build 和 Build And Run 了。

导出之前,要记得更改 Bundle Identifier , Export 项目之后,使用 Android Studio 打开这个项目,File > New > Import project > 选择你刚才导出的目录,并点击 OK 。之后会提示你 Gradle 尚未配置,是否自动配置,点击 OK 会自动配置,点击 Cancel 需要你之后手动配置。

Unity导出Gradle打开后的提示

Unity导出Gradle打开后的提示

我们这里使用自动配置,之后 Android Studio 会运行 Gradle 进行编译,这里可能会报错:

Gradle自动配置时的报错信息

Gradle自动配置时的报错信息

我们打开 Project 面板,双击打开 Gradle Scripts 中的 build.gradle ,修改 Gradle 插件版本到 2.3.3 ,和 Android Studio 版本保持一致。

修改Gradle插件版本

修改Gradle插件版本

修改之后,点击工具栏中的 Sync Project with Gradle Files , Gradle 会重新编译,并且编译通过,不会再遇到问题。

Sync Project with Gradle Files

Sync Project with Gradle Files

打开 Project 面板中的 manifests 文件夹中的 AndroidManifest.xml 文件,注释掉 intent-filter 标签:

注释intent-filter

注释intent-filter

现在,我们继续修改 build.gradle 文件中的内容:

修改Gradle编译方式

修改Gradle编译方式

将编译方式改为 com.android.library ,并注释掉 applicationId ,项目才能被打包成 AAR 。点击菜单栏 Build > Make Project ,Android Studio 会将项目打包成 AAR 文件,并放入 build/outputs/aar 文件夹中。

至此,我们就完成了 Unity 工程打包为 AAR 的所有步骤。

注意,这里 Android Studio 和 Gradle 插件必须是 2.3.3 版本,最新的 Android Studio 3.0.1 使用时会遇到错误,包括导入 AAR 时会提示 AAR 文件过大等错误。由于我不是 Android 开发人员,不知如何解决。

如果在文中发现了 BUG ,或发现某些地方写的不够清楚,欢迎在评论中告诉我,我会完善。

项目Git地址:https://gitee.com/trlanfeng/UnityAndroid/

Unity与Android交互(一):在Android App中加载Android Library中的Activity

开发环境:Android Studio 2.3.3

这篇文章是作为Unity与Android交互系列的第一篇,只讲解如何在Android App中加载一个Android Library中的Activity,并传递参数。在这之前,你需要了解Android Application开发基础。网上有很多入门的Android开发教程。

我们先新建一个Android App工程,命名为AndroidApp,使用Empty Activity作为主Activity。

在菜单栏中点击 File > New > New Module ,在弹出的对话框中选择 Android Library,并命名为AndroidLib,在 Android Studio 左侧的 Project 面板中,右键点击 androidlib ,并选择 New > Activity > Empty Activity ,将新建的 Activity 命名为 LibActivity 以作区分。

我们将 LibActivity 中的 TextView 的 id 改为 lib_textview , text 改为“这是Android Library的Activity”并添加一个 id 为 lib_button 的按钮, text 改为“关闭并传回参数:456”。

在 Project 面板中,右键点击 app ,打开 Open Module Settings ,并在 Dependencies 选项卡中,点击右边的加号,选择 :androidlib ,之后点击 OK 。这时便可以从 App 中调用 Library 中的 Activity 了。

在 MainActivity 添加按钮的 click 事件,用于打开 Android Library 中的 Activity ,在 OnCreate 中加入如下代码:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this,com.test.androidlib.LibActivity.class);
        String message = "123";
        intent.putExtra("com.test.androidapp.message",message);
        startActivityForResult(intent,1);
    }
});

上面的代码用于启动 Android Library 中的 LibActivity 并传递了参数 123 ,参数名为 com.test.androidapp.message (官方推荐参数名前加包名以示区分),值为 123 。

我们需要在 MainActivity 中接收由 LibActivity 返回的数据,在类中加入如下代码:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    TextView main_textview = (TextView) findViewById(R.id.main_textview);
    main_textview.setText(data.getStringExtra("com.test.androidlib.message"));
}

之后在 LibActivity 中接收传递的参数并显示在 TextView 中,并为关闭按钮添加事件并回传参数 456 :

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_lib);

    Intent intent = getIntent();
    String message = intent.getStringExtra("com.test.androidapp.message");
    TextView lib_textview = (TextView) findViewById(R.id.lib_textview);
    lib_textview.setText(message);

    Button button = (Button) findViewById(R.id.lib_button);
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent();
            String message = "456";
            intent.putExtra("com.test.androidlib.message",message);
            setResult(1,intent);
            finish();
        }
    });
}

至此我们就完成在 Android App 中加载一个 Android Library 中的 Activity ,并进行参数传递。目前我们是在 App 项目中新建了一个 Android Library ,我们也可以将 Android Library 导出为 aar 包,并通过 New Module > Import .JAR/.AAR Package 来加入到项目中。

如果在文中发现了 BUG ,或发现某些地方写的不够清楚,欢迎在评论中告诉我,我会完善。

项目Git地址:https://gitee.com/trlanfeng/UnityAndroid/

Unity中制作无限循环滚动的背景

在游戏中,经常会用到需要无限循环滚动的背景,例如打飞机游戏、跑酷游戏等。

本篇文章向你介绍两种简单的制作方法。

方法一:使用Material方式来制作。

优点:简单省内存。

图片导入到Unity中之后,Texture Type使用Texture。如果图片是透明的话,那么勾选Alpha Is Transparency,Wrap Mode更改为Repeat。

图片类型设置为Texture

之后创建一个Material,如果图片是透明的,则Shader选为Unlit/Transparent,如果不是的话,使用Mobile/Diffuse即可。

接下来,创建一个Quad,将材质球拖上去,然后再挂个脚本,脚本里的Update里加一句话即可:

MR.material.mainTextureOffset += new Vector2(Time.deltaTime * moveSpeed * 0.02f, 0);

方法二:两个Sprite进行无限循环。

优点:如果你在做2D游戏,而且需要使其他Sprite与背景保持同等速度会很方便。例如一个无限循环的陆地,上面会随机生成树、石头等,而树和石头需要与陆地保持同样的速度移动。

创建两个Sprite,一个默认position为0,另一个则是0加上图片的宽度,一般Pixel Per Unit为100,图片宽度为2048像素,则第二章图片的x为20.48。

两个Sprite都挂上一个Move脚本,脚本里的Update加几行代码即可:

transform.Translate(Vector3.left * Time.deltaTime * speed);
if (transform.position.x < -20.48f)
{
    transform.position += new Vector3(20.48f * 2, 0, 0);
}

两个方式各有优势,方式一只需要创建一张图片,而方式二需要两张,但是方式二在很常用,至于选择哪个,就看项目的具体需求了。

希望本分享对你有所帮助!

Unity定制Inspector常用的辅助功能

在Unity中,我们可以很方便的对Inspector进行定制,有很多简单方便的小功能都会用到。例如给一个int或float加一个范围等。这些操作不止可以在Editor类型的脚本中进行,在普通的MonoBehaviour中也可以做到。这里给大家分享一下在Unity中试验的结果。

AttributeTest.png

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

//在 Add Component 按钮中增加一个菜单项
[AddComponentMenu("Transform/Follow Transform")]

//在 Create 菜单中,增加一个菜单项,前提条件是:该脚本继承自ScriptableObject
[CreateAssetMenu(fileName = "New AttributeTest File", menuName = "AttributeTest", order =1)]

//限制同一个GameObject只能有一个该组件(脚本)
[DisallowMultipleComponent]

//编辑器的编辑模式下,在Update、OnGUI、 OnRenderObject时会执行
[ExecuteInEditMode]

//自定义组件右上角?图标的链接
[HelpURL("http://fengyu.name/")]

//如果该组件继承自 MonoBehaviour,则必须有一个 BoxCollider 组件同时存在
[RequireComponent(typeof(BoxCollider))]

//没发现任何改变
[SelectionBase]

//只有在该组件继承自 StateMachineBehaviour 时有效,具体作用未知
[SharedBetweenAnimators]

public class AttributeTest : MonoBehaviour
{
    //将一个字段变为颜色原则
    [ColorUsage(true)]
    public Color color;
    
    //脚本管理的地方增加一个菜单
    [ContextMenu("Do Something")]
    void DoSomething()
    {
        Debug.Log("Perform operation");
    }
    
    //字段名称处,增加一个右键菜单。第一个参数为菜单名称,第二个参数为功能的函数名
    [ContextMenuItem("Reset", "ResetBiography")]
    [Multiline(2)]
    public string playerBiography = "";
    void ResetBiography()
    {
        playerBiography = "";
    }
    
    //该值,只有在点击Enter键、丢失焦点时才会被返回
    [Delayed]
    public float delay;
    
    //没有发现产生的影响
    [GUITarget(0, 1)]
	void OnGUI()
	{
		GUI.Label(new Rect(10, 10, 300, 100), "Visible on TV and Wii U GamePad only");
	}
    
    //用于增加一个标题头
    [Header("Header之后的部分")]
    public string header;
    
    //会在 Inspector 中隐藏字段
    [HideInInspector]
    public string hide;
    
    //创建一个显示3行的文本框
    [Multiline(3)]
    public string multiline;
    
    //使值变成滑动条的方式,并限制大小
    [Range(0, 10)]
    public float range;
    
    //加载时初始化运行函数
    [RuntimeInitializeOnLoadMethod]
    static void OnRuntimeMethodLoad()
    {
        Debug.Log("After scene is loaded and game is running");
    }
    
    //可以序列化私有字段,让 private 也在 Inspector 中显示
    [SerializeField]
    private string serializeField;
    
    //创造一个高度为10的空白区域,可以用做分割线,高度单位估计是像素
    [Space(10)]
    public string space;
    
    //创建一个文本区域,文本区域会单独一行存在
    [TextArea]
    public string textArea;
    
    //当字段获得焦点后,鼠标指向字段,会获得的提示信息
    [TooltipAttribute("这是这个字段的提示信息")]
    public string toolTip;
}

Unity3D《拾荒者》学习笔记(一):单例模式

最开始看到Unity官方的《拾荒者》教程时,还是初学Unity,很多知识点都不知道,如今使用Unity工作也有半年了,重新再看教程,很多地方也都能看懂了。于是重新做一下笔记,温故而知新!

单例模式

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。

以上的定义,来自维基百科。

单例模式在游戏开发中是很常用的一种设计模式,下面是在Unity中的一种典型案例:

假如我们的游戏有1-N个游戏关卡(Scene),我们需要一个管理器用来对分数进行管理,在Unity中创建一个名为GameManager的GameObject,在关卡1中,我们创建了这个GameObject,并对分数进行了存储,这时,我们成功过了第一关,需要切换到第二关,切换场景时,默认会销毁当前场景的全部GameObject,而我们需要保留分数信息。这种情况下,我们就需要单例模式来实现这个功能。

public static GameManager instance = null;
void Awake()
{
    if (instance == null)
    {
        instance = this;
    }
    else if (instance != this)
    {
        Destroy(gameObject);
    }
    DontDestroyOnLoad(gameObject);
}

上面的代码就是在Unity中单例模式的完整实现。

Static:

微软的解释是:使用 static 修饰符声明属于类型本身而不是属于特定对象的静态成员。从他人的博客中我看到这样一句解释:表示此方法为所在类或所在自定义类所有,而不是这个类的实例所有。你可以通过这两句话理解Static。

在Awake中,我们首先判断该静态变量是否为null,如果为null,则将当前的类赋值给静态变量。如果不为null且不是当前类,那么说明我们目前有多个GameManager存在,此时需要销毁其他的,只保留之前使用的。通过这种方法,我们就可以通过单例模式来实现分数的管理。当然,不要忘记加上DontDestroyOnLoad,这个是让实例不在场景切换时销毁。

以上就是我对单例模式的学习与理解,如果有不对的地方,欢迎指正!

Unity插件之plyGame教程:DiaQ对话系统

本文为孤月蓝风编写,转载请注明出处:http://fengyu.name/?cat=game&id=296

DiaQ是plyGame旗下的一款对话及任务系统。拥有可视化的对话及任务编辑器,能够很方便的处理对话及任务。但是官方文档却不给力,经过一般研究,终于会使用简单的对话系统了,分享给大家。

首先,你需要安装DiaQ插件,DiaQ有单独的,同时也被包含于plyGame中,至于哪里有的“卖”,请使用伟大的搜索引擎。

在安装DiaQ插件后,菜单栏的Windows中会出现DiaQ的菜单,Tools中会有PL Young菜单,下面会有DiaQ。

我们打开DiaGraphs编辑器,Tools > PL Young > DiaQ > Graph Editor。

图标从左到右依次为:增加、删除、向上移、向下移、修改名称、打开任务编辑器、设置、帮助文档。

我们点击增加,列表中增加一项,之后我们就可以在右边的窗口中创建对话及流程了。选中左侧列表中的一个对话,右边最上方会出现和左边一样的工具条。

从左到右依次为:条件判断、发送消息(SendMessage事件)、设置变量值、等待、调试、任务奖励、完成任务、任务状态、对话。

这一篇中,我们只讲最后一个:对话。

点击对话图标,会在下面视图中创建一个对话框,单机选中对话框,在Unity的Inspector面板中,会显示当前对话框的各种属性。

Dialogure Text:对话内容

Responses:对话选项,例如,NPC询问你是否接受任务,可以回答“接受”和“拒绝”,每一个选项一行,点击右侧的加号添加

Linked Quest:连接到任务

Id:当前对话框的ID

Custom Ident:当前对话的自定义ID,类型为string字符串

Comment:用于对话编辑器中显示的备注信息,用于说明次对话用途,只在编辑器中显示,对话中不显示

Show in Graph:上面的备注信息是否显示在编辑器中

Node MetaData:对话传递的数据,可以在这里传递各种类型的数据,供下一条对话使用

现在,你可以随便创建几个对话内容,并将它们连接起来。你可以点击箭头,会出现连接线,再点击需要连接的对话,两个对话框就会被线连接起来。

在所有对话的内容完成之后,你会在 Assets / plyData / DiaQ 文件夹中看到有一个DiaQ的Prefab,将它添加进游戏场景中。

接下来我们写对话的代码。

首先,你需要:

using plyCommon;
using DiaQ;

其次,我们需要声明一个变量,用于保存获取到的对话内容:

private plyGraph conversation = null;

然后在Start中,获取对话内容(这里你可以通过IdentName两种方式获取,自由选择):

conversation = DiaQEngine.Instance.graphManager.GetGraphByIdent(dialogureID.ToString());

之后,所有的对话,都是在GUI中显示,所以代码均写在OnGUI中:

//判断当前是否有对话正在进行
if (DiaQEngine.Instance.graphManager.ActiveGraph() == null)
{
    //没有对话进行则开始对话
    DiaQEngine.Instance.graphManager.BeginGraph(conversation);
}
else
{
    //获取对话节点
    DiaQNode_Dlg dlg = DiaQEngine.Instance.graphManager.NodeWaitingForData() as DiaQNode_Dlg;
    //如果节点不为null,则调用内容
    if (dlg != null)
    {
        //获取对话内容,并赋值
        string _text = dlg.dialogueText;
        Rect dialogbox = new Rect(0,0,300,300);
        GUI.Box(dialogbox, _text);
        //获取选项数量,并循环所有选项,将选项用按钮输出
        _choicesCount = dlg.responses.Length;
        for (int i = 0; i < _choicesCount; i++)
        {
            //dlg.responses[i]为选项的内容文字
            if (GUI.Button(new Rect(0, i * 45), 100, 50), dlg.responses[i]))
            {
                //根据选项跳转到下一个相连的对话
                DiaQEngine.Instance.graphManager.SendDataToNode(i);
            }
        }
    }
}

这样,我们完整的创建并调用了使用DiaQ制作的对话。