自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

时间:2022-09-23 23:16:35

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的。仅为学习Unity之用。图片大部分是自己画的,少数是从网上搜来的。您可以到我的github页面(https://github.com/bitzhuwei/TankHero-2D)上得到工程源码。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

本篇主要记录关卡解析器、小地图图标和对碰撞的原理的探索,需要耐心分析。

关卡解析器

在一个关卡里,敌方坦克应该是一波一波地出现,每波敌人出现多少个,每个敌人是什么类型的坦克、出现在什么位置都应该是可配置的。这需要一个关卡解析器,把如下的文字解析为一个数据结构 Level 。

 level
{
tank{ } |
tank{ } |
tank{ } |
tank{ } tank{ } tank{ } |
tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } |
tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } |
tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } |
tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } tank{ } tank{ }
}

这段文字的意思是,第一波敌人是类型编号为0,出生位置编号为0的1个坦克;第二波敌人是类型编号为0,出生位置编号为1的1个坦克;。。。

这种东西我喜欢用编译原理解决,因为我在(https://github.com/bitzhuwei/CGCompiler.git)有一个自己写的自动生成词法、语法分析器的工具。关于这个工具的介绍可参考我博客里关于编译原理的文章(在这里搜索"编译器")。

先总结一下关卡的文法

 <Level> ::= "level" "{" <StepList> "}";
<StepList> ::= <Step> <StepList> | null;
<Step> ::= "step" "{" <TankList> "}";
<TankList> ::= <Tank> <TankList> | null;
<Tank> ::= "tank" "{" <TankPrefab> <BornPoint> "}";
<TankPrefab> ::= number;
<BornPoint> ::= number;

然后用工具生成词法语法解析器代码。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

剩下的就是自己写一下从语法树到数据结构的转换。代码如下。

     public static Level GetValue(this SyntaxTree<EnumTokenTypeLevelCompiler,
EnumVTypeLevelCompiler, TreeNodeValueLevelCompiler> syntaxTree)
{
if (syntaxTree == null) { return null; } var result = new Level();
_GetLevel(result, syntaxTree);
return result;
} private static void _GetLevel(Level result, SyntaxTree<EnumTokenTypeLevelCompiler, EnumVTypeLevelCompiler, TreeNodeValueLevelCompiler> syntaxTree)
{
if (syntaxTree.CandidateFunc == LL1SyntaxParserLevelCompiler.GetFuncParsecase_Level___tail_levelLeave())
{
GetTankList(result, syntaxTree.Children[]);
}
} private static void GetTankList(Level level, SyntaxTree<EnumTokenTypeLevelCompiler, EnumVTypeLevelCompiler, TreeNodeValueLevelCompiler> syntaxTree)
{
if (syntaxTree.CandidateFunc == LL1SyntaxParserLevelCompiler.GetFuncParsecase_TankList___tail_tankLeave()
|| syntaxTree.CandidateFunc == LL1SyntaxParserLevelCompiler.GetFuncParsecase_TankList___tail_or_Leave())
{
var egg = GetTank(syntaxTree.Children[]);
level.Add(egg);
GetTankList(level, syntaxTree.Children[]);
}
else if (syntaxTree.CandidateFunc == LL1SyntaxParserLevelCompiler.GetFuncParsecase_TankList___tail_rightBrace_Leave())
{
//nothing to do
} } private static TankEgg GetTank(SyntaxTree<EnumTokenTypeLevelCompiler, EnumVTypeLevelCompiler, TreeNodeValueLevelCompiler> syntaxTree)
{
if (syntaxTree.CandidateFunc == LL1SyntaxParserLevelCompiler.GetFuncParsecase_Tank___tail_tankLeave())
{
var tankPrefab = GetTankPrefab(syntaxTree.Children[]);
var bornPoint = GetBornPoint(syntaxTree.Children[]);
var result = new TankEgg(tankPrefab, bornPoint);
return result;
}
else if (syntaxTree.CandidateFunc == LL1SyntaxParserLevelCompiler.GetFuncParsecase_Tank___tail_or_Leave())
{
var result = new TankEgg(-, -);
return result;
} return null;
} private static int GetBornPoint(SyntaxTree<EnumTokenTypeLevelCompiler, EnumVTypeLevelCompiler, TreeNodeValueLevelCompiler> syntaxTree)
{
if (syntaxTree.CandidateFunc == LL1SyntaxParserLevelCompiler.GetFuncParsecase_BornPoint___numberLeave())
{
var result = int.Parse(syntaxTree.Children[].NodeValue.NodeName);
return result;
} return ;
} private static int GetTankPrefab(SyntaxTree<EnumTokenTypeLevelCompiler, EnumVTypeLevelCompiler, TreeNodeValueLevelCompiler> syntaxTree)
{
if (syntaxTree.CandidateFunc == LL1SyntaxParserLevelCompiler.GetFuncParsecase_TankPrefab___numberLeave())
{
var result = int.Parse(syntaxTree.Children[].NodeValue.NodeName);
return result;
} return ;
}

GetLevel

在VS2013里你可以获得Tip,便于coding。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

解析器写好了,调用方式如下。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

语法分析器的类型太长,只好用上图表示一下。

这样就有敌方坦克一波一波来袭的感觉了。

小地图图标

小地图上显示的坦克很不清晰,如果能显示出一个鲜艳的三角形就好了,尖头指向开炮的方向。如下图所示,绿色的为玩家坦克,红色的为敌方坦克。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

首先给坦克的prefab增加一个显示箭头的子对象。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

给子对象smallMapTankHero增加Sprite Renderer组件,在组件的Sprite属性里赋予下面的图片,并把此对象的Layer属性设置为自定义的"smallCamera"(Layer的名字无所谓)。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

Hierarchy里的smallCamera对象是用来显示小地图的。设置其Culling Mask属性如下图所示,勾选smallCamera。这样,箭头就会显示在smallCamera里。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

相应的,在主摄像机Main Camera里,设置Culling Mask属性如下图所示,取消勾选smallCamera。这样,箭头就不会显示在smallCamera里。

有点平行世界异次元的感觉。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

小地图里仍旧会显示原有的坦克贴图,为了挡住坦克贴图,我们把小地图图标向上(靠近摄像机的方向)移动一点点。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

当然,也可以通过把坦克对象的Layer设置为一个自定义的Layer(比如自定义为realworld),然后在smallCamera的Culling Mask属性中取消勾选realworld即可。不过这还需要把各种对象都放到自定义的Layer里去,太麻烦了。

碰撞

Unity中的碰撞,有Collision(物理碰撞)和Trigger(触发器碰撞)两种。

这里我用精心设计的试验分析出了碰撞的产生条件。

触发碰撞的基本条件

下面,假设Hierarchy中有两个对象A和B,A和B都有Collider组件和Rigidbody组件。那么:

如果去掉其中任何一个的Collider,那么不会发生碰撞事件(只会穿透)。

如果去掉B的rigidbody,那么移动B去撞A时,不会发生碰撞事件(只会穿透)。

如果去掉B的rigidbody,那么移动A去撞B时,Collision和Trigger的碰撞都可以发生在A和B身上,但B不会受物理引擎影响而移动。

OnTriggerXXX的触发原则

下面,再假设A是Cube,B是sphere;A有3个Box Collider,设置第2个Box Collider的Is Trigger为true;B有4个Shpere Collider,设置第2、3个Sphere Collider的Is Trigger为true。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

然后,给A和B分别添加如下的脚本组件:

 public class TriggerTest : MonoBehaviour
{ // Use this for initialization
void Start()
{
} // Update is called once per frame
void Update()
{
} void OnTriggerEnter(Collider other)
{
Debug.Log(string.Format("{0} trigger {1}'s({2})", this.name, other.name, other.GetInstanceID()));
} void OnCollisionEnter(Collision collision)
{
Debug.Log(string.Format("{0} collision {1}'s({2})", this.name, collision.transform.name, collision.collider.GetInstanceID()));
}
}

这样就可以记录自己碰撞了对方的哪个Collider。

我们先让A撞B,再让B撞A,分别得到如下图左右所示的结果。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

分析发现,虽然引发的碰撞事件的先后顺序有所不同,但碰撞事件是相同的,都是那16个Trigger事件和2个Collision事件。

据此我总结出Trigger事件的触发原则,如下图所示。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

当A的第2个Collider设置为Is Trigger=true时,此Collider会与B的各个Collider都引发一次Trigger碰撞事件。B也同理。不过两边均为Is Trigger=true时,只会引发一次。数一下上图,可以看到有8条线,每条线在AB两方各引发一次Trigger碰撞事件,所以就是上文的16次。另外,每个Collider引发的次数也与上文的图示吻合。

OnCollisionXXX的触发条件

下面,我把A和B的所有Collider的Is Trigger都设为false,来研究Collision的触发条件。

下图展示了去掉B的Rigidbody后,分别用A撞B和用B撞A的情况。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

可以看到,没有Rigidbody的B撞A,什么都不会发生,B直接穿透了A。

再用含有2个Collider的Cube和含有2个Collider的Sphere试验(此处略)就可以知道,A撞B则会使A的第1个Collider依次与B的各个Collider引发Collision碰撞事件。所以上图左侧会有1个A的Collider与4个B的Collider引发的Collision事件。

继续看下图,是同时具有Rigidbody的A和B相互碰撞的结果。

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

可以看到A和B都只有1次Collision事件。

根据上述试验,我认为Unity3D引擎在处理Collision事件时,是去找AB双方的Rigidbody,如果找到了就让它执行物理引擎;如果都找到了,此次碰撞就告结束,不再引发此次A和B的Collision事件。

总结

我查了Unity一些资料,总结了一下Unity中关于碰撞的原则:

physics will not be applied to objects that do not have Rigidbodies attached.
Kinematic Rigidbody 自身不受外力,但仍旧可对其它物体产生力。

您可以到我的github页面(https://github.com/bitzhuwei/TankHero-2D)上得到工程源码。

请多多指教~

自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析的更多相关文章

  1. 自制Unity小游戏TankHero-2D&lpar;3&rpar;开始玩起来

    自制Unity小游戏TankHero-2D(3)开始玩起来 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的.仅 ...

  2. 自制Unity小游戏TankHero-2D&lpar;1&rpar;制作主角坦克

    自制Unity小游戏TankHero-2D(1)制作主角坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...

  3. 自制Unity小游戏TankHero-2D&lpar;2&rpar;制作敌方坦克

    自制Unity小游戏TankHero-2D(2)制作敌方坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...

  4. 自制Unity小游戏TankHero-2D&lpar;5&rpar;声音&plus;爆炸&plus;场景切换&plus;武器弹药

    自制Unity小游戏TankHero-2D(5)声音+爆炸+场景切换+武器弹药 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm ...

  5. Unity小游戏制作 - 暗影随行

    用Unity制作小游戏 - 暗影惊吓 最近玩了一个小游戏,叫做暗影惊吓,虽然是一个十分简单的小游戏,但是感觉还是十分有趣的.这里就用Unity来实现一个类似的游戏. 项目源码:DarkFollow 主 ...

  6. 教你用Python自制拼图小游戏,一起来制作吧

    摘要: 本文主要为大家详细介绍了python实现拼图小游戏,文中还有示例代码介绍,感兴趣的小伙伴们可以参考一下. 开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Pyt ...

  7. Java自制人机小游戏——————————剪刀、石头、布

    package com.hello.test; import java.util.Scanner; public class TestGame { public static void main(St ...

  8. Kinect&plus;unity 实现体感格斗闯关小游戏

    文章目录 项目地址 1 项目概况 1.1 项目简介 1.2 项目目的 1.3 主要技术 2 设计 2.1 基本概念 2.2 框架 2.3 算法 2.4 模型 2.5 调查问卷 3 实现 3.1 技术难 ...

  9. 使用Unity制作游戏关卡的教程(一)

    转自: http://gamerboom.com/archives/74131 作者:Matthias Zarzecki 我正在制作<Looking For Group – The Fork O ...

随机推荐

  1. ASP&period;NET MVC原理

    仅此一文让你明白ASP.NET MVC原理   ASP.NET MVC由以下两个核心组成部分构成: 一个名为UrlRoutingModule的自定义HttpModule,用来解析Controller与 ...

  2. 读书笔记——《图解TCP&sol;IP》&lpar;3&sol;4&rpar;

    经典摘抄 第五章 IP协议相关技术 1.DNS可以将网址自动转换为具体的IP地址. 2.主机识别码的识别方式:为每台计算机赋以唯一的主机名,在进行网络通信时,可以直接使用主机名称而无需输入一大长串的I ...

  3. 2016年11月12日 星期六 --出埃及记 Exodus 20&colon;3

    2016年11月12日 星期六 --出埃及记 Exodus 20:3 "You shall have no other gods before me.除了我以外,你不可有别的 神.

  4. IE下easyui 缓存问题

    $.ajaxSetup ({   cache: false //关闭AJAX相应的缓存 }); 这一句话就足够了,很管用!

  5. MySQL学习笔记(2)

    打开数据库 USE db_name; SELECT DATABASE();查看当前所选中的数据库 创建数据表 CREATA TABLE [IF NOT EXISTS] table_name ( col ...

  6. 团队作业8——第二次项目冲刺(Beta阶段) 5&period;19

    Day1--5.19 1.展开站立式会议(拍摄者:武健男): 会议内容:(1)新成员自我介绍,使大家能更快熟悉并一起合作. (2)由于我们之前的项目经理去了别的小组,所以我们投票选取新成员林乔桦作为我 ...

  7. 学习之路-前端-笔记-一、HTML笔记

    各种技巧 1.在Webstrom中 同时按ctrl+alt+insert创建新内容 2.输入标签按tab自动补全 按end 或 HOME实现光标移动到当前行的最后或最前 3.按住alt键不放同时按鼠标 ...

  8. Nginx系列4:用GoAccess实现可视化并实时监控access日志

    1.ubuntu16.04安装GoAccess GoAccess下载地址:https://goaccess.io/download 安装步骤: $ wget https://tar.goaccess. ...

  9. android 之 Hnadler 、Message 、Looper

    Handler定义: 主要接受子线程发送来的数据,并用此数据配合主线程更新UI. 为什么要用Handler? 我们手机当中的很多功能或操作是不能都放在Activity当中的,比如下载文件.处理大量数据 ...

  10. 手机移动端web前端常见问题整理

    移动端常见问题及解决方案 一.meta基础知识 H5页面窗口自动调整到设备宽度,并禁止用户缩放页面 <meta name="viewport" content="w ...