unity3D游戏开发实战(二)——道具
目录
- 准备工作
- 旁白改进
- 玩法具体描述
- 第一关
- 脚本编写
- 道具
准备工作
旁白改进
在上一次的制作中,旁白的字幕有些些微的问题,如果Text在三维空间中与摄像机之间有物体遮挡,字母的一部分会被遮住,我尝试了改变Canvas的Sorting Layers,但是仍有字幕遮挡现象发生,在搜索了很久后,我终于找到了解决办法,需要重新编写着色器,来让字幕总是优先显示于最上层。
首先新建Stantard Surface Shader文件,并在其中写入以下代码:
Shader "Custom/UIShader"
{Properties{[PerRendererData] _MainTex("Font Texture", 2D) = "white" {}_Color("Tint", Color) = (1,1,1,1)_StencilComp("Stencil Comparison", Float) = 8_Stencil("Stencil ID", Float) = 0_StencilOp("Stencil Operation", Float) = 0_StencilWriteMask("Stencil Write Mask", Float) = 255_StencilReadMask("Stencil Read Mask", Float) = 255_ColorMask("Color Mask", Float) = 15}SubShader{LOD 100Tags{"Queue" = "Transparent""IgnoreProjector" = "True""RenderType" = "Transparent""PreviewType" = "Plane""CanUseSpriteAtlas" = "True"}Stencil{Ref[_Stencil]Comp[_StencilComp]Pass[_StencilOp]ReadMask[_StencilReadMask]WriteMask[_StencilWriteMask]}Cull OffLighting OffZWrite OffZTest AlwaysOffset -1,-1Blend SrcAlpha OneMinusSrcAlphaColorMask[_ColorMask]Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#include "UnityUI.cginc"struct appdata_t{float4 vertex : POSITION;float2 texcoord : TEXCOORD0;float4 color : COLOR;};struct v2f{float4 vertex : SV_POSITION;half2 texcoord : TEXCOORD0;fixed4 color : COLOR;};sampler2D _MainTex;float4 _MainTex_ST;fixed4 _Color;fixed4 _TextureSampleAdd;v2f vert(appdata_t v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);o.color = v.color * _Color;#ifdef UNITY_HALF_TEXEL_OFFSETo.vertex.xy += (_ScreenParams.zw - 1.0) * float2(-1,1);#endifreturn o;}fixed4 frag(v2f i) : SV_Target{fixed4 col = (tex2D(_MainTex, i.texcoord) + _TextureSampleAdd) * i.color;clip(col.a - 0.01);return col;}ENDCG}}
}
之后新建Material材质球,修改其着色器为刚刚编写的着色器,并将其赋给Text,之后字幕就会永远显示在画面的最外层了。
玩法具体描述
为了让工作内容与目标变得更加清晰,需要重新具体地描绘一遍玩法以及特殊机制:
- 整个场景没有全局光照,只有玩家自带的点光源;
- 在接触到(互动)部分物体后,这部分物体会变亮;
- 因为视觉受限,寻找物体的主要方式依靠听觉,所以会为特殊的物品添加声源;
- 与特殊物品互动后,会得到相应的道具或者触发机关推动关卡的进行;
- 特殊物体发光后,可以照亮部分黑暗地区,让原本模糊的关键道具变得可见;
- 设置一些隐藏房间和对通关无关紧要的奖励/彩蛋房间,设置成就;
- 在完成这些的基础上,添加关卡计时系统与存档/读档功能,添加主界面UI。
第一关
作为游戏的第一个关卡,其内容一定要简单明了,让玩家在轻松通关的同时掌握游戏的玩法,如果说出生时的房间是为了让玩家学会走路,那么第一关的职责就是让玩家认识这个世界,因此我并没有采取特别复杂的编排方式,我绘制了一个简单的关卡草图来描述第一个关卡:
第一关没有什么需要抉择的难题,玩家前往十字岔路时,过关的道路看似死胡同,而另一侧的门是上锁的,玩家只能前往右侧的房间。
因为保险柜不会发出声音而且环境幽暗,所以即使玩家进入左边的房间也不会有收获,这时候需要用旁白来引导玩家离开这个房间,在玩家进入另一个房间后,会循着声音找到喷泉与水瓶架子,玩家自然会明白用水瓶去装水。
但是这时候问题来了,装起来的水有什么用呢?
在玩家迷茫之际,泉水和架子的干扰声消失,只要玩家触碰过这两个物体,保险柜就会发出金属声响,引导玩家前往另一个房间,此时保险柜已经被喷泉点亮的灯光照耀现行,因此玩家就进入了输入密码的环节,而唯一的提示就是架子的水平数量,当玩家解开谜题后就会获得钥匙,之后的解密就变得一气呵成,玩家会经过开门——倒水——触发机关——根据声音找到打开的机关门——沿着楼梯过关的顺序通过第一关,第一关的初步设计就这样做好了。
脚本编写
道具
经过刚才的设计,很明显我们要为玩家设计一个道具系统,使用道具栏的方式会有很多局限性,例如彩蛋物品放在哪里、道具持有达到上限卡关等问题,因此不予采纳。
而背包使用起来较为繁琐,代码编写也比较复杂,我们仍然不去采用。
考虑到游戏本身操作并不复杂,涉及不到多个道具同时使用的情况,我决定把道具栏设为滚动式,只有一个当前道具栏位,使用切换键可以切换当前道具。
而道具的使用方式和互动键可以是同一按键,所以在编写道具脚本的同时,也要改变一下物体之间的交互方式。
因为代码过长,我将分区域进行展示:
[Header("ImgInstance")]public GameObject itemNow;public GameObject itemNext;public GameObject itemPrev;public GameObject itemTmp;[Header("Parameters of Long Click")]public float startChange;public float changeSpan;[Header("Flames")]public Image flameNow;public Image flameNext;public Image flamePrev;public Image flameTmp;[Header("MoveAnimation")]public float moveTime;[Range(0,1f)]public float moveSpeed;[Range(0, 1f)]public float shadeSpeed;[Range(0, 1f)]public float scaleSpeed;public Transform PosNow;public Transform PosPrev;public Transform PosNext;public Transform PosTmpR;public Transform PosTmpL;public float alpha1;public float alpha2;[HideInInspector] public int num = 0;private List<int> items = new List<int>();private int moveDirection = 0; //0不动,1向右,-1向左private float startTime;private float lastChange = 0;private float startMove;private Image itemNowImg;private Image itemNextImg;private Image itemPrevImg;private Image itemTmpImg;private Color nowColor = new Color(1, 1, 1, 1);private Color subColor;private Color tmpColor;
首先是变量的声明,因为我将道具栏切换的UI代码也写在了道具脚本中,所以会有大量的Transform实例和速度参数等数据,其中比较重要的是num,这个数字代表着当前的道具栏位位置,不仅涉及到UI动画,也涉及到交互逻辑,故特此将其标记出来。
private void RollItem(int dir){GameObject tmpItem;Image tmpFlame;if (dir == 1){tmpItem = itemNow;itemNow = itemPrev;itemPrev = itemTmp;itemTmp = itemNext;itemNext = tmpItem;tmpFlame = flameNow;flameNow = flamePrev;flamePrev = flameTmp;flameTmp = flameNext;flameNext = tmpFlame;}else{tmpItem = itemNow;itemNow = itemNext;itemNext = itemTmp;itemTmp = itemPrev;itemPrev = tmpItem;tmpFlame = flameNow;flameNow = flameNext;flameNext = flameTmp;flameTmp = flamePrev;flamePrev = tmpFlame;}itemNowImg = itemNow.GetComponent<Image>();itemNextImg = itemNext.GetComponent<Image>();itemPrevImg = itemPrev.GetComponent<Image>();itemTmpImg = itemTmp.GetComponent<Image>();}
之后是道具栏的滚动,我对道具栏动画的处理方式为,平时显示三个道具栏,在按下切换按钮后生成一个临时的道具栏从滚动方向的起点出现,原本在最后的道具栏渐隐。
但是每次都重新生成再销毁实例效率很低,所以要生成的道具栏会以透明度为0的方式隐藏在屏幕上,这时在滚动道具栏后,原本道具栏的实例对应的位置会发生变化,所以要用RollItem函数将其按切换方向依次替换。
private void InstantiateNewItemImg(int dir){itemTmp.transform.position = dir == 1 ? PosTmpL.position : PosTmpR.position;itemTmp.transform.localScale = PosTmpR.localScale;if (items.Count == 0)itemTmpImg.sprite = Resources.Load("Img/0", typeof(Sprite)) as Sprite;else if (items.Count == 1){itemTmpImg.sprite = Resources.Load("Img/" + items[0], typeof(Sprite)) as Sprite;}else if (items.Count == 2){print("Img/" + items[1 - num]);itemTmpImg.sprite = Resources.Load("Img/" + items[1 - num], typeof(Sprite)) as Sprite;}else{int imgID = num + dir;if (imgID >= items.Count)imgID -= items.Count;else if (imgID < 0)imgID += items.Count;itemTmpImg.sprite = Resources.Load("Img/" + items[imgID], typeof(Sprite)) as Sprite;}}
之后是生成新道具栏图片的代码,说是生成,其实也不过是替换了贴图并调整了生成的位置,因为在不同地区都会用到这个功能,所以封装成了函数。
public void AddItem(int id){Sprite img = (Sprite)Resources.Load("Img/" + id, typeof(Sprite));items.Add(id);if (items.Count == 1)itemNowImg.sprite = img;else if (items.Count == 2){itemNextImg.sprite = img;itemPrevImg.sprite = img;}else if (items.Count == 3){if (num == 0)itemPrevImg.sprite = img;elseitemNextImg.sprite = img;}else{if (num == 0)itemPrevImg.sprite = img;if (num == items.Count - 1)itemNextImg.sprite = img;}}public void UseItem(){if (items.Count == 1){itemPrevImg.sprite = Resources.Load("Img/0", typeof(Sprite)) as Sprite;itemNextImg.sprite = Resources.Load("Img/0", typeof(Sprite)) as Sprite;itemNowImg.sprite = Resources.Load("Img/0", typeof(Sprite)) as Sprite;items.RemoveAt(num);}else if (items.Count == 2){items.RemoveAt(num);num = 0;itemPrevImg.sprite = Resources.Load("Img/" + items[0], typeof(Sprite)) as Sprite;itemNextImg.sprite = Resources.Load("Img/" + items[0], typeof(Sprite)) as Sprite;itemNowImg.sprite = Resources.Load("Img/" + items[0], typeof(Sprite)) as Sprite;}else if (items.Count == 3){items.RemoveAt(num);num %= 2;itemPrevImg.sprite = Resources.Load("Img/" + items[1 - num], typeof(Sprite)) as Sprite;itemNextImg.sprite = Resources.Load("Img/" + items[1 - num], typeof(Sprite)) as Sprite;itemNowImg.sprite = Resources.Load("Img/" + items[num], typeof(Sprite)) as Sprite;}else{items.RemoveAt(num);if (num == items.Count){num = 0;itemPrevImg.sprite = Resources.Load("Img/" + items[items.Count - 1], typeof(Sprite)) as Sprite;itemNextImg.sprite = Resources.Load("Img/" + items[1], typeof(Sprite)) as Sprite;itemNowImg.sprite = Resources.Load("Img/" + items[0], typeof(Sprite)) as Sprite;}else if (num == items.Count - 1){itemNextImg.sprite = Resources.Load("Img/" + items[0], typeof(Sprite)) as Sprite;itemNowImg.sprite = Resources.Load("Img/" + items[num], typeof(Sprite)) as Sprite;}else{itemNextImg.sprite = Resources.Load("Img/" + items[num + 1], typeof(Sprite)) as Sprite;itemNowImg.sprite = Resources.Load("Img/" + items[num], typeof(Sprite)) as Sprite;}}}
之后是添加道具和使用道具的代码,会切换图片并对num进行相应的处理,不过这两个函数在Item脚本中并没有遇到,是为了在与物体交互获得道具/消耗道具时调用的,所以要设置成public函数。
private void Start(){itemNowImg = itemNow.GetComponent<Image>();itemNextImg = itemNext.GetComponent<Image>();itemPrevImg = itemPrev.GetComponent<Image>();itemTmpImg = itemTmp.GetComponent<Image>();subColor = new Color(1, 1, 1, alpha1);tmpColor = new Color(1, 1, 1, alpha2);}private void Update(){if (Input.GetKeyDown(KeyCode.Q)){if (moveDirection == -1){startMove = 2 * Time.time - startMove - moveTime;num--;RollItem(moveDirection);if (num < 0)num += items.Count;}else if (moveDirection == 0){InstantiateNewItemImg(1);startMove = Time.time;num--;if (num < 0)num += items.Count;}if (items.Count == 0)num = 0;moveDirection = 1;startTime = Time.time;}else if (Input.GetKeyDown(KeyCode.E)){if (moveDirection == 1){startMove = 2 * Time.time - startMove - moveTime;num++;RollItem(moveDirection);if (num == items.Count)num -= items.Count;}else if (moveDirection == 0){InstantiateNewItemImg(-1);startMove = Time.time;num++;if (num == items.Count)num -= items.Count;}if (items.Count == 0)num = 0;moveDirection = -1;startTime = Time.time;}if (Input.GetKey(KeyCode.Q)){if (Time.time - startTime >= startChange){if (Time.time - lastChange >= changeSpan){num--;if (num < 0)num += items.Count;if (items.Count == 0)num = 0;InstantiateNewItemImg(1);moveDirection = 1;startMove = Time.time;lastChange = Time.time;}}}else if (Input.GetKey(KeyCode.E)){if (Time.time - startTime >= startChange){if (Time.time - lastChange >= changeSpan){num++;if (num == items.Count)num -= items.Count;if (items.Count == 0)num = 0;InstantiateNewItemImg(-1);moveDirection = -1;startMove = Time.time;lastChange = Time.time;}}}if (moveDirection != 0){itemNow.transform.position = Vector3.Lerp(itemNow.transform.position, moveDirection == 1 ? PosNext.position : PosPrev.position, moveSpeed);itemNext.transform.position = Vector3.Lerp(itemNext.transform.position, moveDirection == 1 ? PosTmpR.position : PosNow.position, moveSpeed);itemPrev.transform.position = Vector3.Lerp(itemPrev.transform.position, moveDirection == 1 ? PosNow.position : PosTmpL.position, moveSpeed);itemTmp.transform.position = Vector3.Lerp(itemTmp.transform.position, moveDirection == 1 ? PosPrev.position : PosNext.position, moveSpeed);itemNow.transform.localScale = Vector3.Lerp(itemNow.transform.localScale, moveDirection == 1 ? PosNext.localScale : PosPrev.localScale, scaleSpeed);itemNext.transform.localScale = Vector3.Lerp(itemNext.transform.localScale, moveDirection == 1 ? PosTmpR.localScale : PosNow.localScale, scaleSpeed);itemPrev.transform.localScale = Vector3.Lerp(itemPrev.transform.localScale, moveDirection == 1 ? PosNow.localScale : PosTmpL.localScale, scaleSpeed);itemTmp.transform.localScale = Vector3.Lerp(itemTmp.transform.localScale, moveDirection == 1 ? PosPrev.localScale : PosNext.localScale, scaleSpeed);itemNowImg.color = Color.Lerp(itemNowImg.color, subColor, shadeSpeed);itemNextImg.color = Color.Lerp(itemNextImg.color, moveDirection == 1 ? tmpColor : nowColor, shadeSpeed);itemPrevImg.color = Color.Lerp(itemPrevImg.color, moveDirection == 1 ? nowColor : tmpColor, shadeSpeed);itemTmpImg.color = Color.Lerp(itemTmpImg.color, subColor, shadeSpeed);flameNow.color = itemNowImg.color;flamePrev.color = itemPrevImg.color;flameNext.color = itemNextImg.color;flameTmp.color = itemTmpImg.color;if (Time.time - startMove >= moveTime){RollItem(moveDirection);moveDirection = 0;}}}
之后就是Update函数,通过e和q来作为切换道具的扳机,按下不动会持续切换道具栏,道具的脚本就写好了。
当然初始的道具栏是空的,所以三个道具栏的默认贴图设成空白就好了,剩下的就是布置场景了。
如图所示,做好四个道具框,并将tmp透明度调为0用来过渡动画,并新建五个空物体放在positions中用来定位。
将自己绘制的道具图片资源(道具序号即为存在Item中的items数组内容)放在Resources文件夹下,为了方便测试我提前在脚本中为玩家加入了这三个道具,我们点击运行来看一下实现效果。
已经成功实现了动态的图片读取,动画效果也很顺畅,道具脚本就编写完成了。
但是我们还没有实现道具的交互,下一期将会编写交互的底层代码,并实现道具的使用和获取,感谢大家的阅读。
unity3D游戏开发实战(二)——道具相关推荐
- unity3D游戏开发实战(四)——使用道具与密码锁
目录 改进 对话代码 鼠标指针 获得当前道具 完成喷泉房间 置物架 喷泉 道具添加代码修改 密码解锁获得道具 用UI来实现输入密码 设置UI界面 密码锁代码 遮罩与按钮响应挂载 返回与输入密码 改进 ...
- unity3D游戏开发实战原创视频讲座系列7之消消乐游戏开发
解说文件夹 第一讲 游戏介绍和资源简单介绍 第二讲 游戏场景背景的搭建 第三讲 游戏特效预制体的制作 第四讲 游戏场景前景的显示 第五讲 瓷砖背景块 第六讲 方块的消除 第七讲 方块的交 ...
- unity3D游戏开发实战原创视频讲座系列9之塔防类游戏开发第一季
解说文件夹 塔防游戏0基础篇... 第一讲 游戏演示和资源介绍... 第二讲 游戏场景的完毕... 第三讲 预制体的制作... 第四讲 敌人的随机产生和按路径行走... 第五讲 塔防工具的产 ...
- unity3D游戏开发十二之疯狂的小球
下面我们通过一个具体的实例来了解如何使用物理引擎,该实例通过第三人称视角控制游戏中的小球对象,游戏通过是否与钻石碰撞来界定是否寻找到钻石并获得积分,获得积分满10分后,赢得游戏,当小球冲出跑道时,游戏 ...
- unity3D游戏开发实战原创视频讲座系列13之帽子戏法游戏开发(预告)
文件夹 第一讲 游戏演示项目创建...1 第二讲 游戏场景的编辑...1 第三讲 帽子的移动...2 第四讲 炮弹的产生...4 第六讲 游戏界面的完好...6 第七讲 各种UI的制作...8 ...
- unity3D游戏开发实战(五)——声音
目录 机关房间制作 门锁脚本挂载 空气墙 声音 AudioSource介绍 生成声音与声音消失 机关房间制作 门锁脚本挂载 我们剩余的工作量已经所剩无几,只需要将门锁挂载给机关房间即刻,我们摆放好物体 ...
- unity3D游戏开发实战(一)——角色移动与对白
目录 游戏理念 游戏底层构建 初始房间设计 旁白脚本 游戏理念 欲制作的游戏为一款3D解密游戏,通过剥夺玩家的视觉,来增强听觉的重要性,让玩家在黑暗中依据声音进行探索,并且随着探索点亮部分地区,最终让 ...
- Android开发入门——推箱子游戏开发实战(十二)
绘制游戏局面 本文是推箱子游戏程序开发的第七步.系列文章前五篇描述准备工作,故本文编号是(十二).本文讲解如何绘制游戏局面. 本文目标 本文讲解如何绘制游戏局面.游戏局面的示例如图1,图2所示.这两幅 ...
- Unity3D游戏开发之使用Unity3D开发2D游戏(二)(2DTookit插件亲测)
大家好,今天博主继续为大家带来Unity3D游戏开发系列文章,我们接着在上一篇文章中最后留下的那几个问题来讲解Unity3D游戏开发的相关知识.在上一篇文章最后,我们留了这样几个问题: 1.人物范围控 ...
最新文章
- 华为实施微服务架构的五大军规
- memcached全面剖析–4. memcached的分布式算法
- Android中常用的编码和解码(加密和解密)的问题
- python 扑克牌中的顺子
- CString比较相等不得不说的故事
- 关系数据库SQL之可编程性存储过程
- Windows 10封装中出现“无法验证你的Windows安装”错误解决方法
- mysql查看主键别名_MySQL怎么查看约束的别名呢?
- 24 种设计模式之 观察者模式
- 7-Flink的分布式缓存
- php对接银行接口,php 银行接口开发写法
- python整数缓存机制
- Unity3D 5.3 新版AssetBundle使用方案及策略
- Git管理工具SourceTree文件预览乱码问题
- 用python做曲_谁在用 python 弹奏一曲《菊花台》
- 三菱四节传送带控制梯形图_四节传送带控制
- 10分钟带你彻底搞懂企业服务总线
- Java面向对象OOP思想概述
- 基于资源的权限系统-数据库设计
- 基于pytorch的双模态数据载入
热门文章
- python imshow函数_opencv学习之显示图像-imshow函数
- 收集IIS配置错误-- 您未被授权查看该页
- ACCESS学习日记(一.ACCESS 的对象)
- sqrt函数原型c语言,C语言sqrt函数的实例用法讲解
- 计算机技术在设计中的应用浅论,论计算机技术在美术设计中的应用
- 偏最小二乘回归预测(MATLAB源码)
- linux查看系统运行时间及启动时间
- 查看和修改Linux的时区
- 微信公众号开发(二):利用责任链和模板方法模式设计消息的处理流程
- Kyligence Zen 产品体验 — 一站式指标平台助力电商数字化转型