在Unity环境下使用抽象和接口
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环境下使用抽象和接口的更多相关文章
- YApi——手摸手,带你在Win10环境下安装YApi可视化接口管理平台
手摸手,带你在Win10环境下安装YApi可视化接口管理平台 YApi YApi 是高效.易用.功能强大的 api 管理平台,旨在为开发.产品.测试人员提供更优雅的接口管理服务.可以帮助开发者轻松创建 ...
- Windows7 64下搭建Caffe+python接口环境
参考链接: http://www.cnblogs.com/yixuan-xu/p/5858595.html http://www.cnblogs.com/zf-blog/p/6139044.html ...
- 项目部署到liunx环境下访问接口返回异常
1.访问接口返回异常 已经连续踩了两次这个坑了.所以记下来了.方便下次搜索! 项目在window下运行正常,无任何异常! 但是部署到liunx环境下的服务器上就有问题 访问静态页面毫无问题,一旦涉及到 ...
- 为啥我喜欢在Windows 7环境下做Unity开发?
先说明,以下情况只针对本人哦~ 前阵子我在OSX的最新版本Mavericks下做Unity开发,后来我把MacbookPro卖了,自己组装了个PC搞开发,为啥呢? 1:OSX下 MonoDevelop ...
- IIS服务器环境下某路径下所有PHP接口无法运行报500.19错误
IIS服务器环境下某路径(文件夹)下所有PHP接口无法运行报500.19错误 环境:IIS8.5 + php7.2.1 错误描述:某目录下(如 d:\web\A)所有php接口文档运行错误,接口测试工 ...
- WP8_(windows phone环境下)上传文件从C#到php接口
在windows phone环境下,将手机上的图片上传到服务端(php环境): 注意事项:在上传的地方,头文件中name,例如name= img,则在php服务端处理时 ,需要约定好 存取一致 php ...
- spring Boot环境下dubbo+zookeeper的一个基础讲解与示例
一,学习背景 1. 前言 对于我们不管工作还是生活中,需要或者想去学习一些东西的时候,大致都考虑几点: a) 我们为什么需要学习这个东西? b) 这个东西是什么? c) ...
- Java面向对象理解_代码块_继承_多态_抽象_接口
面线对象: /* 成员变量和局部变量的区别? A:在类中的位置不同 成员变量:在类中方法外 局部变量:在方法定义中或者方法声明上 B:在内存中的位置不同 成员变量:在堆内存 局部变量:在栈内存 C:生 ...
- .NET环境下有关打印页面设置、打印机设置、打印预览对话框的实现
原文:.NET环境下有关打印页面设置.打印机设置.打印预览对话框的实现 我个人认为,开发MIS,首先就得解决网格的问题,而开发工具为我们提供了如DataGrid.MSHFlexGrid的控件.其次,是 ...
随机推荐
- C++进程间通信
# C++进程间通信 # 进程间通讯的四种方式:剪贴板.匿名管道.命名管道和邮槽 ## 剪切板 ## //设置剪切板内容 CString str; this->GetDlgItemText(ID ...
- Checkbox 模板和样式
<Style TargetType="{x:Type CheckBox}"> <Setter Property="FontFamily" Va ...
- Unmanaged Exports使用方法
Unmanaged Exports,可以利用C#生成非托管的DLL文件. 从https://sites.google.com/site/robertgiesecke/下载UnmanagedExport ...
- 在MongoDB的MapReduce上踩过的坑
太久没动这里,目前人生处于一个新的开始.这次博客的内容很久前就想更新上来,但是一直没找到合适的时间点(哈哈,其实就是懒),主要内容集中在使用Mongodb时的一些隐蔽的MapReduce问题: 1.R ...
- offset、client、scroll开头的属性归纳总结
HTML元素有几个offset.client.scroll开头的属性,总是让人摸不着头脑.在书中看到记下来,分享给需要的小伙伴.主要是以下几个属性: 第一组:offsetWidth,offsetHei ...
- SSH框架整合
SSH框架整合 一.原理图 action:(struts2) 1.获取表单的数据 2.表单的验证,例如非空验证,email验证等 3.调用service,并把数据传递给service Service: ...
- Atitit.软件架构高扩展性and兼容性原理与概论实践attilax总结
Atitit.软件架构高扩展性and兼容性原理与概论实践attilax总结 1. 什么是可扩展的应用程序?1 2. 松耦合(ioc)2 3. 接口的思考 2 4. 单一用途&模块化,小粒度化2 ...
- centos 6 YUM安装 lmp
准备篇: 1.配置防火墙,开启80端口.3306端口 vi /etc/sysconfig/iptables -A INPUT -m state --state NEW -m t ...
- 突然想到一个问题:有关 cqrs 分离
大部分的系统,都是查询多,c少,那为什么我们不把q放在内存里面,而c直接操作数据库呢? 就如enode,c在内存,而q在数据库,当然q也很容易扩展到内存缓存上.二个enode案例demo,都可以让c的 ...
- 织梦DedeCMS模板防盗的四种方法
织梦(DedeCMS)模板也是一种财富,不想自己辛辛苦苦做的模板被盗用,在互联网上出现一些和自己一模一样的网站,就需要做好模板防盗.本文是No牛收集整理自网络,不过网上的版本都没有提供 Nginx 3 ...