目录

  • 准备工作
    • 旁白改进
    • 玩法具体描述
    • 第一关
  • 脚本编写
    • 道具

准备工作

旁白改进

在上一次的制作中,旁白的字幕有些些微的问题,如果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,之后字幕就会永远显示在画面的最外层了。

玩法具体描述

为了让工作内容与目标变得更加清晰,需要重新具体地描绘一遍玩法以及特殊机制:

  1. 整个场景没有全局光照,只有玩家自带的点光源;
  2. 在接触到(互动)部分物体后,这部分物体会变亮;
  3. 因为视觉受限,寻找物体的主要方式依靠听觉,所以会为特殊的物品添加声源;
  4. 与特殊物品互动后,会得到相应的道具或者触发机关推动关卡的进行;
  5. 特殊物体发光后,可以照亮部分黑暗地区,让原本模糊的关键道具变得可见;
  6. 设置一些隐藏房间和对通关无关紧要的奖励/彩蛋房间,设置成就;
  7. 在完成这些的基础上,添加关卡计时系统与存档/读档功能,添加主界面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游戏开发实战(二)——道具相关推荐

  1. unity3D游戏开发实战(四)——使用道具与密码锁

    目录 改进 对话代码 鼠标指针 获得当前道具 完成喷泉房间 置物架 喷泉 道具添加代码修改 密码解锁获得道具 用UI来实现输入密码 设置UI界面 密码锁代码 遮罩与按钮响应挂载 返回与输入密码 改进 ...

  2. unity3D游戏开发实战原创视频讲座系列7之消消乐游戏开发

    解说文件夹 第一讲  游戏介绍和资源简单介绍 第二讲  游戏场景背景的搭建 第三讲  游戏特效预制体的制作 第四讲  游戏场景前景的显示 第五讲  瓷砖背景块 第六讲  方块的消除 第七讲  方块的交 ...

  3. unity3D游戏开发实战原创视频讲座系列9之塔防类游戏开发第一季

    解说文件夹 塔防游戏0基础篇... 第一讲  游戏演示和资源介绍... 第二讲  游戏场景的完毕... 第三讲  预制体的制作... 第四讲  敌人的随机产生和按路径行走... 第五讲  塔防工具的产 ...

  4. unity3D游戏开发十二之疯狂的小球

    下面我们通过一个具体的实例来了解如何使用物理引擎,该实例通过第三人称视角控制游戏中的小球对象,游戏通过是否与钻石碰撞来界定是否寻找到钻石并获得积分,获得积分满10分后,赢得游戏,当小球冲出跑道时,游戏 ...

  5. unity3D游戏开发实战原创视频讲座系列13之帽子戏法游戏开发(预告)

    文件夹 第一讲  游戏演示项目创建...1 第二讲 游戏场景的编辑...1 第三讲  帽子的移动...2 第四讲  炮弹的产生...4 第六讲  游戏界面的完好...6 第七讲 各种UI的制作...8 ...

  6. unity3D游戏开发实战(五)——声音

    目录 机关房间制作 门锁脚本挂载 空气墙 声音 AudioSource介绍 生成声音与声音消失 机关房间制作 门锁脚本挂载 我们剩余的工作量已经所剩无几,只需要将门锁挂载给机关房间即刻,我们摆放好物体 ...

  7. unity3D游戏开发实战(一)——角色移动与对白

    目录 游戏理念 游戏底层构建 初始房间设计 旁白脚本 游戏理念 欲制作的游戏为一款3D解密游戏,通过剥夺玩家的视觉,来增强听觉的重要性,让玩家在黑暗中依据声音进行探索,并且随着探索点亮部分地区,最终让 ...

  8. Android开发入门——推箱子游戏开发实战(十二)

    绘制游戏局面 本文是推箱子游戏程序开发的第七步.系列文章前五篇描述准备工作,故本文编号是(十二).本文讲解如何绘制游戏局面. 本文目标 本文讲解如何绘制游戏局面.游戏局面的示例如图1,图2所示.这两幅 ...

  9. Unity3D游戏开发之使用Unity3D开发2D游戏(二)(2DTookit插件亲测)

    大家好,今天博主继续为大家带来Unity3D游戏开发系列文章,我们接着在上一篇文章中最后留下的那几个问题来讲解Unity3D游戏开发的相关知识.在上一篇文章最后,我们留了这样几个问题: 1.人物范围控 ...

最新文章

  1. 华为实施微服务架构的五大军规
  2. memcached全面剖析–4. memcached的分布式算法
  3. Android中常用的编码和解码(加密和解密)的问题
  4. python 扑克牌中的顺子
  5. CString比较相等不得不说的故事
  6. 关系数据库SQL之可编程性存储过程
  7. Windows 10封装中出现“无法验证你的Windows安装”错误解决方法
  8. mysql查看主键别名_MySQL怎么查看约束的别名呢?
  9. 24 种设计模式之 观察者模式
  10. 7-Flink的分布式缓存
  11. php对接银行接口,php 银行接口开发写法
  12. python整数缓存机制
  13. Unity3D 5.3 新版AssetBundle使用方案及策略
  14. Git管理工具SourceTree文件预览乱码问题
  15. 用python做曲_谁在用 python 弹奏一曲《菊花台》
  16. 三菱四节传送带控制梯形图_四节传送带控制
  17. 10分钟带你彻底搞懂企业服务总线
  18. Java面向对象OOP思想概述
  19. 基于资源的权限系统-数据库设计
  20. 基于pytorch的双模态数据载入

热门文章

  1. python imshow函数_opencv学习之显示图像-imshow函数
  2. 收集IIS配置错误-- 您未被授权查看该页
  3. ACCESS学习日记(一.ACCESS 的对象)
  4. sqrt函数原型c语言,C语言sqrt函数的实例用法讲解
  5. 计算机技术在设计中的应用浅论,论计算机技术在美术设计中的应用
  6. 偏最小二乘回归预测(MATLAB源码)
  7. linux查看系统运行时间及启动时间
  8. 查看和修改Linux的时区
  9. 微信公众号开发(二):利用责任链和模板方法模式设计消息的处理流程
  10. Kyligence Zen 产品体验 — 一站式指标平台助力电商数字化转型