http://gamasutra.com/blogs/VictorBarcelo/20131217/207204/Using_abstractions_and_interfaces_with_Unity3D.php

Unity3D includes a component architecture paradigm. This allows us to attach code as classes that derive from MonoBehaviour to our GameObjects and treat them as script components.

With Unity3D GameObjects can communicate with scripts via SendMessage by just addressing the name of particular methods. We can also get the reference to the script with GetComponent. This brings us the safety of type checking but makes us rely on a concrete script reference which results in tight coupling (this can actually not be the case if the class inside the script inherits from some kind of abstraction, more on this later).

Besides the Unity3D component mojo we also have more classical approaches in the OO department that may suit our needs depending on the scenario.

One of them is to use an observer pattern based system, such as the one found in the Unity3D wiki, which is a fastest and cleaner alternative to Unity’s SendMessage. This solution is suitable if we wanted to communicate with a number of GameObjects without having to manage concrete references for each one.

We also have the alternative of using abstract references (abstract classes and interfaces). With these we can make our code less coupled and we can plug reusable logic without relying on concrete classes.

The following example shows how can you combine this approach with Unity's component based nature.

All the code and a working example can be found in this GitHub repository.

Abstracting SHUMP entities

If you try to categorize the most used type of logic in a SHUMP you may agree with me if I point “movement patterns”. Any type of projectiles, enemies and power ups will move not always in a linear fashion. You may have diagonal, senoidal or saw tooth movement patterns and you are likely to combine them to make complex sequences that can for example fit a boss encounter.

Let’s start by creating a suitable interface for our movement algorithms.

public interface IMovement
{
void Move(GameObject entity);
}

Now let’s show two movement patterns that implement this interface.

using UnityEngine;

public class LinearMovement : IMovement
{
public void Move(GameObject go)
{
go.transform.Translate(Time.deltaTime * 10, 0f, 0f);
}
}
using UnityEngine;

public class SenoidalMovement : IMovement
{
private const float amplitude = 1f;
private const float frequency = 2f; public void Move(GameObject go)
{
float yMovement = amplitude * (Mathf.Sin(2 * Mathf.PI * frequency * Time.time) - Mathf.Sin(2 * Mathf.PI * frequency * (Time.time - Time.deltaTime)));
go.transform.Translate(Time.deltaTime * 10f, yMovement, 0f);
}
}

Neither the interface nor the movement logic need to be added to GameObjects since they don’t inherit from MonoBehaviour.

For this example our moving entity will be missiles, so let’s create a base class for them.

using UnityEngine;

public class BaseMissileBehaviour : MonoBehaviour
{
private IMovement movementType; void Start()
{
Destroy(gameObject, 2f);
} void Update()
{
movementType.Move(gameObject);
} public void SetMovement(IMovement _movementType)
{
movementType = _movementType;
}
}

This script has to be attached to a GameObject as a component. From here we can create a collection of prefabs to set up different missile visuals such as the model or particle systems.

Let's keep abstracting entities for the sake of reusability and create a weapon interface.

public interface IWeapon
{
void Shoot();
}

Now let’s get concrete, I give you the laser cannon.

using UnityEngine;

public class LaserCannon : IWeapon
{
private float fireDelay;
private float timeSinceLastShoot;
private GameObject owner;
public GameObject projectilePrefab; public LaserCannon(float _fireDelay, GameObject _owner)
{
owner = _owner;
fireDelay = _fireDelay;
} public void Shoot()
{
if (Time.time > fireDelay + timeSinceLastShoot)
{
GameObject projectile =
(GameObject)
GameObject.Instantiate(projectilePrefab, owner.transform.position, Quaternion.identity);
projectile.GetComponent().SetMovement(new LinearMovement());
timeSinceLastShoot = Time.time;
}
}
}

This way of structuring our code gives us the following benefits.

  • We can reuse movement logic, just as we did in BaseMissileBehavior we can plug this logic onto any moving entity. This way of using interfaces is known as strategy pattern.
  • We can encapsulate abstract entities (missiles) inside concrete entities (weapons). This let's us choose at which level we can assign attributes (like damage or fire delay in this example). In this case note that the overall configuration (which missile we use and which movement we plug inside them) takes place at the weapon level via its constructor.
  • We can ask another class to give us instances of predefined weapons by supplying a name (preferably enum) or id. This class could query a data source (e.g. XML or SQLite) for the specific configuration of the given weapon. This is an application of the factory pattern. You can apply the same idea to entities like enemies (tip: if you are continuously creating entities with a low life span add to your factory object pooling to avoid performance penalty).

And finally this is how an entity could make use of a weapon via an interface.

using System.Collections.Generic;
using UnityEngine; public class PlayerShip : MonoBehaviour
{
private IWeapon activeWeapon;
private List weapons; void Start()
{
weapons = new List { new MissileLauncher(0.5f, gameObject), new LaserCannon(0.5f, gameObject) };
} public void Control()
{
if (Input.GetKey(KeyCode.Space))
{
SetWeapon(weapons[0]);
activeWeapon.Shoot();
}
if (Input.GetKey(KeyCode.RightControl))
{
SetWeapon(weapons[1]);
activeWeapon.Shoot();
}
} private void SetWeapon(IWeapon _weapon)
{
activeWeapon = _weapon;
}
}

This is a good example since you can see how easy it is to switch concrete classes based on its interface during runtime.

You can use this approach to create a movement pattern based of multiple movement behaviors as we discussed early. You just have to cycle through a collection of movement implementations every xtime which is something easily doable with coroutines.

GetComponent and abstractions

Actually GetComponent can fetch a script component by its superclass or interface implementation. I will show you an example of a scenario where you could use this, but first here you have some useful extension methods.

using System.Collections.Generic;
using System.Linq;
using UnityEngine; internal static class ExtensionMethods
{
public static T GetAbstract(this GameObject inObj) where T : class
{
return inObj.GetComponents().OfType().FirstOrDefault();
} public static T GetInterface(this GameObject inObj) where T : class
{
if (!typeof(T).IsInterface)
{
Debug.LogError(typeof(T).ToString() + ": is not an actual interface!");
return null;
}
return inObj.GetComponents().OfType().FirstOrDefault();
} public static IEnumerable GetInterfaces(this GameObject inObj) where T : class
{
if (!typeof(T).IsInterface)
{
Debug.LogError(typeof(T).ToString() + ": is not an actual interface!");
return Enumerable.Empty();
}
return inObj.GetComponents().OfType();
}
}

Let's picture a typical mouse driven action rpg. You may click on a door and you player will move to its proximity and the door will open. You could create a ISOpenable interface with an Open method. Since different doors may have a different opening logic (sliding, rotating etc) you could reference any type of door by its interface.

To make this work you could check for every mouse click if the mouse is hovering a GameObject that implements ISOpenable and call a GetInterface().Open();

You could also use this approach to attack enemies with something likeGetInterface().ApplyDamage(playerDamage); and so on.

Going further with abstractions

The art of plugging concrete classes on to abstractions is called dependency injection, and relies in the idea of inversion of control which is one of the core techniques of maintainable OO code.

If you want to go further in the practice of adding OO spice to your Unity3D development I recommend taking a look at StrangeIoC which is a framework that gives us a variety of tools, being one of them a cleaner mechanism to manage injections.

在Unity环境下使用抽象和接口的更多相关文章

  1. YApi——手摸手,带你在Win10环境下安装YApi可视化接口管理平台

    手摸手,带你在Win10环境下安装YApi可视化接口管理平台 YApi YApi 是高效.易用.功能强大的 api 管理平台,旨在为开发.产品.测试人员提供更优雅的接口管理服务.可以帮助开发者轻松创建 ...

  2. Windows7 64下搭建Caffe+python接口环境

    参考链接: http://www.cnblogs.com/yixuan-xu/p/5858595.html http://www.cnblogs.com/zf-blog/p/6139044.html ...

  3. 项目部署到liunx环境下访问接口返回异常

    1.访问接口返回异常 已经连续踩了两次这个坑了.所以记下来了.方便下次搜索! 项目在window下运行正常,无任何异常! 但是部署到liunx环境下的服务器上就有问题 访问静态页面毫无问题,一旦涉及到 ...

  4. 为啥我喜欢在Windows 7环境下做Unity开发?

    先说明,以下情况只针对本人哦~ 前阵子我在OSX的最新版本Mavericks下做Unity开发,后来我把MacbookPro卖了,自己组装了个PC搞开发,为啥呢? 1:OSX下 MonoDevelop ...

  5. IIS服务器环境下某路径下所有PHP接口无法运行报500.19错误

    IIS服务器环境下某路径(文件夹)下所有PHP接口无法运行报500.19错误 环境:IIS8.5 + php7.2.1 错误描述:某目录下(如 d:\web\A)所有php接口文档运行错误,接口测试工 ...

  6. WP8_(windows phone环境下)上传文件从C#到php接口

    在windows phone环境下,将手机上的图片上传到服务端(php环境): 注意事项:在上传的地方,头文件中name,例如name= img,则在php服务端处理时 ,需要约定好 存取一致 php ...

  7. spring Boot环境下dubbo+zookeeper的一个基础讲解与示例

    一,学习背景 1.   前言 对于我们不管工作还是生活中,需要或者想去学习一些东西的时候,大致都考虑几点: a)      我们为什么需要学习这个东西? b)     这个东西是什么? c)      ...

  8. Java面向对象理解_代码块_继承_多态_抽象_接口

    面线对象: /* 成员变量和局部变量的区别? A:在类中的位置不同 成员变量:在类中方法外 局部变量:在方法定义中或者方法声明上 B:在内存中的位置不同 成员变量:在堆内存 局部变量:在栈内存 C:生 ...

  9. .NET环境下有关打印页面设置、打印机设置、打印预览对话框的实现

    原文:.NET环境下有关打印页面设置.打印机设置.打印预览对话框的实现 我个人认为,开发MIS,首先就得解决网格的问题,而开发工具为我们提供了如DataGrid.MSHFlexGrid的控件.其次,是 ...

随机推荐

  1. 剖析 HTTP 协议

    HTTP 概述 HTTP 是什么? HTTP(HyperText Transfer Protocol,超文本传输协议)是WWW (World Wide Web)实现数据通信的基石. HTTP是由IET ...

  2. 百度EChart3初体验

    由于项目需要在首页搞一个订单数量的走势图,经过多方查找,体验,感觉ECharts不错,封装的很细,我们只需要看自己需要那种类型的图表,搞定好自己的json数据就OK.至于说如何体现出来,官网的教程很详 ...

  3. 在CentOS 7上安装.NET Core R2跑Hello World

    前言 在上个月.NET Core出了最新版本预览版,只是在Window系统上试验了一下.原本想等发布正式版的时候在linux系统上试试,可能还需要一段时间,刚好有空可以折腾一下. 由于之前安装的Ubu ...

  4. xUnit入门一

    看了下Nhibernate的入门Demo,感觉测试驱动开发会更效率.当然,你可能觉得不是还要额外编程单元测试代码吗?开发怎么会更效率? 一句话解释之,磨刀不误砍柴工. 那就开始入门吧 ~.~ 笔者使用 ...

  5. PHP基础知识第三趴

    今天如约放送函数部分吧,毕竟预告都出了,"广电"也没禁我......

  6. JVM之内存结构

    JVM是按照运行时数据的存储结构来划分内存结构的.JVM在运行Java程序时,将他们划分成不同格式的数据,分别存储在不同的区域,这些数据就是运行时数据.运行时数据区域包括堆,方法区,运行时常量池,程序 ...

  7. eclipse SE增加Web开发插件

    最近接触了些java项目,之前安装了eclipse SE版本.没有Web开发插件,调试不了Web代码.点击“Window”--“Preference” 左边菜单栏是找不到“Server”项来配置服务器 ...

  8. Android 内存泄漏的一些情况。

    最近在维护代码,发现一个自定义View(这个View是在一个AsyncTask的工作线程doInBackground中新建的,在UI线程onPostExecute中添加进window中的)经常会泄漏内 ...

  9. ios App与网页交互

    随着移动APP的快速迭代开发趋势,越来越多的APP中嵌入了html网页,但在一些大中型APP中,尤其是电商类APP,html页面已经不仅仅满足展示功能,这时html要求能与原生语言进行交互.相互传值. ...

  10. Android开发学习—— 消息队列

    ###主线程不能被阻塞* 在Android中,主线程被阻塞会导致应用不能刷新ui界面,不能响应用户操作,用户体验将非常差* 主线程阻塞时间过长,系统会抛出ANR异常* ANR:Application ...