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. visual formatting model (可视化格式模型)【持续修正】

    概念: visual formatting model,可视化格式模型 The CSS visual formatting model is an algorithm that processes a ...

  2. "检索COM类工厂中 CLSID为 {00024500-0000-0000-C000-000000000046}的组件时失败,原因是出现以下错误: 80070005" 问题的解决

    一.故障环境 Windows 2008 .net 3.0 二.故障描述 ​ 调用excel组件生成excel文档时页面报错.报错内容一大串,核心是"检索COM类工厂中 CLSID为 {000 ...

  3. VMware12下安装Debian8.5

    参考:  Debian 8.2.0 (Jessie) 快速纯净安装教程    Debian 7 安装配置总结    Debian 7.8 系统安装配置过程 软件包管理命令    包命令    从源代码 ...

  4. enote笔记语言(1)

    what                 是什么 why                  为什么 when                何时 where               在哪里 whi ...

  5. Eclipse Meaven Spring SpringMVC Mybaits整合

    本示例是在:Ubuntu15上实现的:Windows上安装Maven将不太相同. Maven Install Run command sudo apt-get install maven, to in ...

  6. 扩展JS Date对象时间格式化功能

    在自己JS代码中引入一下代码: Date.prototype.format =function(format) { var o = { "M+" : this.getMonth() ...

  7. Java设计模式 -- 基本原则

    这两个星期开始系统地学习设计模式相关的知识,对每一个原则或者设计模式主要从下面几点分析学习: 定义:简单地描述其作用 解决问题:说明该原则或设计模式解决什么限制条件下的问题. 结构图:绘制相关例子的U ...

  8. 苹果的MDM简介

    MDM(Mobile Device Management)移动设备管理,一般会用于企业管理其移动设备,鉴于iOS是比较封闭的系统很多的功能都难以实现,所以利用苹果的MDM可以达到远程控制设备,像远程定 ...

  9. android:布局、绘制、内存泄露、响应速度、listview和bitmap、线程优化以及一些优化的建议!

    1.布局优化 首先删除布局中无用的控件和层级,其次有选择地使用性能较低的viewgroup,比如布局中既可以使用RelativeLayout和LinearLayout,那我们就采用LinearLayo ...

  10. android 自定义动画

    android自定义动画注意是继承Animation,重写里面的initialize和applyTransformation,在initialize方法做一些初始化的工作,在applyTransfor ...