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. Rafy 框架 - 幽灵插件(假删除)

      Rafy 框架又添新成员:幽灵插件.本文将解释该插件的场景.使用方法.原理.   场景 在开发各类数据库应用系统时,往往需要在删除数据时不是真正地删除数据,而只是把数据标识为'已删除'状态.这些数 ...

  2. ionic2+angular2中踩的那些坑

    好久没写什么东西了,最近在做一个ionic2的小东西,遇到了不少问题,也记录一下,避免后来的同学走弯路. 之前写过一篇使用VS2015开发ionic1的文章,但自己还没摸清门道,本来也是感兴趣就学习了 ...

  3. Chart: Who pays the most in Seattle for software engineers

    http://www.geekwire.com/2012/chart-pays-seattle-software-engineers/ Chart: Who pays the most in Seat ...

  4. shiro实现session共享

    session共享:在多应用系统中,如果使用了负载均衡,用户的请求会被分发到不同的应用中,A应用中的session数据在B应用中是获取不到的,就会带来共享的问题. 假设:用户第一次访问,连接的A服务器 ...

  5. WCF实现事件通知相关应用技巧介绍

    WCF实现事件通知是一个比较容易掌握的知识点,不过在实现的过程中,我们还是需要注意一些事项,以保证功能的完善性. WCF中有一些方法的应用对于初学者来说还是比较容易应用.只要熟练的联系这些方法操作,一 ...

  6. SQL联合查询:子表任一记录与主表联合查询

    今天有网友群里提了这样一个关于SQL联合查询的需求: 一.有热心网友的方案: 二.我的方案: select * from ( select a.*,(select top 1 Id from B as ...

  7. iOS报错[__NSCFNumber length]: unrecognized

    出现这种报错很大的原因是因为类型给错了,或许你这个数据是从json上解析后得到的,但是需要看一下这个数据是NSString还是NSNumber类型,如果是NSNumber类型的话,你又直接使用NSSt ...

  8. Android中使用ImageViewSwitcher实现图片切换轮播导航效果

    前面写过了使用ViewFlipper和ViewPager实现屏幕中视图切换的效果(ViewPager未实现轮播)附链接: Android中使用ViewFlipper实现屏幕切换 Android中使用V ...

  9. Atitit.工作流 与 规则引擎

    Atitit.工作流 与 规则引擎 1.1. 应用来说,通常分为三部分:界面.业务逻辑和存储1 1.2. 自定义操作系列1 1.3. 自定义按钮系列2 1.1. 应用来说,通常分为三部分:界面.业务逻 ...

  10. lucky 的 时光助理

    2017年的lucky小姐,厌倦了现在的工作,她觉得这些的工作对于她而言不具备挑战性,她在迷茫春节过后该如何选择, 这里是距她走出校门整整一年的时光. lucky小姐从开发走向了实施,目的是想周游这个 ...