week2 day 8-10

摄像头设置

在 Window > Package Manager里可加入新的包,这里加入cinemachine,为摄像头的功能包。在顶部 Cinemachine > Create 2D Camera可创建一个虚拟的摄像头Vitural camera,通常真实摄像头会在不同VC中切换以达到不同视角,这里2D单纯就一个视角即可。通常摄像头有两种视角:

这里因为是2D cinema所以自动是orthographic(直角)。摄像头的orthographic size通常设为整个height的一半以正好容纳整个画面(eg. 如果画面高度为20unit,则size设为10),而画面宽度根据比例 4:3/16:9 自动决定。

为了让摄像头跟随ruby,在摄像头的follow里拖入ruby即可。之后为了设置摄像头的边际bound则可在extension里加入CinemachineConfiner,这里给它加入一个多(四)边形碰撞( Polygon Collider 2D)即可。创建一个空的gameObject作为collider的载体,碰撞域设为全体tilemap以让摄像头限制在tilemap。但此时bounder作为collider会把ruby/robot撞开,所以单独给bounder设一个layer并在phycial setting中取消bounder和任何layer的碰撞。最后把object扔到vc的confiner的bounding属性中即可。

特效(Particle)

在Effects > Particle System里新建一个特效smokeEffect,其核心属性有:

  • System:

    • looping:循环生成,在一次lifetime后重新生成
    • start lifetime/speed: 生产时间/速度,可设置为在区间内生成随机数来更在自然
    • simulation space:local即特效不随物体移动,world模式下特效会随物体移动
  • Emission: brusts一次性生成多个sprite

  • shape:

    • angle:生成角度
    • radius:生成半价
  • Color over lifetime: 点击空白会产生一个渐变器,上方alpha为透明度,下方为颜色属性。左边是start状态,右边是end状态,这里吧end的alpha设为0(全透明)即可。

  • Size over lifetime: 改成递减曲线,以达到逐步变小

  • Texture Sheet Animation: 将mode改为sprite以供用sprite显示特效,在之后的sprite里加入。使用radom随机生成两种烟雾sprite以更好地显示效果。

接下来就是如何把特效停止的问题。很简单从sprite的controller中设置公共变量

public ParticleSystem smokeEffect;

之后将smokeEffect拖入这个slot即可。

这里值得注意的是smokeEffect是个有ParticleSystem的gameObject,而你吧它拖入了ParticleSystem变量槽里。这种简化的方法不仅方便了你不用去script里用GetComponent<>去找组成,也防止了gameObject里没有这个Component,总之是个很好用的方法。

最后在Fixed function中使用Stop方法自然地停止特效。

smokeEffect.Stop(); //如果使用Destory(effect),特效会立马消失

同样地对于生成特效,先在script里把shootEffect drag到solt里,再使用Instantiating函数生成特效。

week2 day11-12

UI Canvas(帆布)

设计UI即显示一些文字图片信息,这里主要使用的概念是Canvas,即画布。在hierachy里创建一个Canvas,同时生成一个EventSystem。这里主要是Canvas的渲染属性:

  • overlay: default,在游戏界面上方覆盖显示
  • worldspace: 自由模式,通常用于3D

尺寸属性有:

  • Constant Size:固定大小
  • Scale With Screen Size:根据实际分辨率调整

这里使用简单的固定大小。


在画布上的UI需要加在Canvas里作为子类,在选中canvas后创建image物体。

把sprite拖入image里即可,可以使用Set Native Size把image大小设为sprite原大小。使用T来调整image大小。把image拖到左上角,但这仅仅是snap到对应位置而不是place。

所以接下来使用画布的锚Anchor的概念:

image的position是由anchor到pivot的向量决定的,所以当画面被resize时position会保持相同所以导致image超出整个画布的边界(border)。解决方案是将anchor放在同样的角落即可。

设置好BAR的图片后,还要在BAR上加入角色图片和血量条。同样需要把他们设为BAR的子类。我们希望在拉伸时保持图片局限在一个范围内,使用Rect Transform里的rect即可。

在设置healthBar时,需要使用mask。先使用一张空白的image覆盖到BAR的health部分,同时需要调整Pivot(蓝色小圈)到左边以供伸缩时以左边为支点。mask相当于给image一个隐藏范围。

想要调整pivot需要把上方的center模式调整为pivot

在mask里创建image即可,之后按住ALT后stretch图片就会变成填充。这样就可以把image充满mask。之后需要隐藏mask画布,在component里加入mask取消show mask即可。这样通过调整隐藏的mask就可以完成对填充的healthBar的伸缩。

之后使用script的公共变量获取mask并调整其大小:

mask.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, originalSize * value);

使用静态变量来完成全局变量:

public static UIHealthBar instance { get; private set; } //设置权限
void Awake()
{
    instance = this;
}

之后就可以在rubyController里使用:

UIHealthBar.instance.SetValue(currentHealth / (float)maxHealth);

RayCasting(射线机制)

首先创建NPC的sprite并设置好Animator,并加入box collider,将碰撞域设为中下部分。

之后使用物理系统的射线机制,从一个点沿一个方向发射一定长度的射线,并判断是否有碰撞(hit).

RaycastHit2D hit = Physics2D.Raycast(rigidbody2d.position + Vector2.up * 0.2f, lookDirection, 1.5f, LayerMask.GetMask("NPC"));
if (hit.collider != null)
{
    Debug.Log("Raycast has hit the object " + hit.collider.gameObject);
}

最后一个param是碰撞图层,我们把NPC单独放在一个layer以达到ray只会碰撞NPC层的物品(触发对话)。

对话框

对话框作为text,也是需要在canvas上显示,不同的是需要在触发对话后才能被激活。

在sprite底下创造canvas,这里需要调成world space,通过设置position和scale来调成适当大小/位置。在canvas上加入全覆盖(ALT + stretch)的dialogUI图片,之后再创建子类TextMeshPro以显示文字。

最后就是控制dialog的显示,同理给NPC一个script来控制,使用public获取canvas(gameObject)并使用SetActive函数来控制显示,同时需要Time.deltaTime和counter变量来计时。最后设置显示函数DisplayDialog来激活BOX和重置counter,每次使用raycasting在hit后调用DisplayDialog即可。

音频(Audio)

主体分为三部分:

  • Audio Clips: 音片,即每个播放的音乐文件
  • Audio Listener: 音频收听者,通常指camera
  • Audio Source: 音频发起者

背景音乐

在hierarchy中创建一个source为BGM,设置其为LOOP,将Spatial Blend调为2D(即各处播放声音一样,与linster和source距离无关)。

简单音效

在这里播放音效主要是用audioSource的内置函数playshot。将Source调为2D,发起音效大致为:

先确定声音发起者,给其一个component:AudioSource。在发起者中start获取,之后便可以使用playshot来播放不同的clip。clip可由public变量从外部获取到script里,再由不同的判定场景来决定放哪个clip。

if(ruby is damaged) audioSource.playshot(damageAduioClip);
if(ruby is healthed) audioSource.playshot(HealthAduioClip);

空间音效(Spatialization)

假设机器人走路会发出声音,而我们听到的脚步声会随距离听到的音量大小不同,则需要使用Spatialization。

首先给机器人一个AudioSource,将Spatial Blend调为3D,就会出现最小声音范围的圈,即在圈内均为MAX音量。在3D setting中吧max distance调为10unit,即在10unit时会消退为0,在其中min-max范围内会逐步衰减。

最后需要调整作为Listener的main camera(MC),因为camera本来就在平面视角的上方(俯视视角),当然camera随ruby移动我们边可以认为是ruby听到的。所以我们需要把camera在z=0的投影(即投在平面的位置)作为listener。在MC下创建子类listener将z=10即相对camera在下面10个unit正好是ruby平面。之后取消MC自己的listener。由于子类关系listener的移动会随着MC移动。

build game

在project setting里设置图标,名字等属性后,可在build setting选好平台即可发布。

SUMMARY

tech stack

开学两周内算是跑完了第一个demo,写的比较详细因为不懂嘛,基础还是要巩固下的。总的来说c#还是很简单的没什么特殊的,除了变量规定那边设置变量读取权限可能陌生些其他都是正常逻辑流程。总的来说这部分教程分为以下几步:

  • 如何将一(几)张图(sprite)通过分割后做成一个prefabs(也就是gameObject),记得设置好动画效果,在animator的state machine的设计。tilemap的画地图也很关键! *
  • script基本东西,像start/awake区别,如何设置只读变量(*)/公共变量,使用Time.deltaTime来计时,vector2和速度来控制移动,特别是向量的运算
  • collider和rigidbody,以及关于碰撞域的设置,碰撞函数的灵活应用。之后也用到了物理系统的addForce来扔导弹,raycast来触发对话。
  • 如何编写受伤/加血以及其他事物的逻辑流程,特定的固有函数(Instantiate)还需要熟悉。 *(想起当年那个圣经)
  • 摄像头问题。如何跟着ruby走啊(Virtual Camera的follow),如何固定摄像头的范围(VC的Confined Mode)
  • UI问题,canvas的设置包括mask还是很复杂的,像图形变化时调整pivot和anchor来防止变形,还有他的rect Transform(边界范围)。
  • 特效那个一堆设置,audio的三要素(source放clip给listener)和空间音效(声音随距离减少)的设置

outlook

本来做到一半以为unity只是提供流程控制,游戏的精髓:流畅度(我认为)是靠帧来决定的,所以核心在美工。但很快发现好像自己想多了,先不说圣经里面乱七八糟的东西,就一个raycast的百度就引出了一堆射线机制的讨论问题,看来各种物理机制的水还是很深的。接下来会做大三那时候粗糙带过的rougelike了,也是会过一遍教程。但之后blog就不会记录那么细了,不会再记录操作机制而仅仅是总结核心技术。研究生课程虽说不算紧但还是得花点功夫去搞搞的,辅导那边也快结束了得详细看看了。


Comments

2019-09-17

⬆︎TOP