unity入门项目Ruby‘s Adventure

前言

Ruby's Adventure是unity官方提供的一个案例,当初入门就是做的这个项目,学到了很多unity的基本操作。这篇文章记录了这个游戏从创建项目到发布的过程。

游戏简介:城镇里的机器人都因为缺少零件而失控,我们的主角狐狸Ruby收到青蛙先生的委托后,拿上了齿轮,去修复失控的机器人。

一、游戏效果

玩家可以用W、A、S、D控制Ruby移动,Ruby在触碰到失控的机器人和陷阱后生命值会降低,在和青蛙先生对话后可以按H发射齿轮,齿轮命中机器人后机器人会被修好。

二、前期准备

1.创建项目

用Unity Hub创建一个2d项目

2.资源导入

在Unity官方的AssetStore可以免费获取资源

获取资源后可以在Unity的Package Manager中导入资源

三、人物创建

3.1创建人物和脚本

拖入主角Ruby的图片素材,拖入的物体叫精灵(sprite)

新建一个脚本RubyController并挂载在人物上,然后就可以写脚本来控制Ruby了。

在visula studio中可以看到,Unity会自动生成RubyController类,并有Start()和Update()两个函数,Start()会在项目的第一帧调用一次,Update()会在之后的每一帧调用一次

3.2实现主角的移动

获取键盘输入,并用rigidbody2d移动主角,这样在按下W、A、S、D后Ruby就会移动了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class RubyController : MonoBehaviour
{private Rigidbody2D rigidbody2d;// Start is called before the first frame updatevoid Start(){rigidbody2d = GetComponent();}// Update is called once per framevoid Update(){//获取键盘的轴向输入float horizontal = Input.GetAxis("Horizontal");float vertical = Input.GetAxis("Vertical");//创建一个position记录移动后的位置Vector2 position = transform.position;//改变x,y轴上位置position.x = position.x + 3 * horizontal * Time.deltaTime;position.y = position.y + 3 * vertical * Time.deltaTime;//将主角的位置置为移动后的位置rigidbody2d.MovePosition(position);}
}

四、场景布局

4.1背景布置

为了能够快速布置游戏背景,可以用Tilemap工具来构建瓦片地图,创建瓦片地图时,Grid组件自动作为瓦片地图的父级,相比于传统使用图片搭建地图的方式,使用瓦片地图用来搭建地图更快。

添加后发现场景上出现了很多格子,可以使用Tile Palette里的Tile在上面绘制,Tilemap可以理解为画布,Tile Palette可以理解为调色板,Tile就是调色板上的颜料。

现在Tilemap中还没有Tile,先将资源切割,添加到Tile Palette中

然后用Tile Palette就可以方便的绘制2D场景了

4.2设置渲染层级

我们看到背景覆盖了我们的主角Ruby,导致我们看不到我们的Ruby了,因为它们的Order in Layer都是0

为了让Ruby在背景之上,将Tilemap的Order in Layer设为-10,因为背景一定在最下面

4.3丰富游戏场景

游戏只有背景显然是不够的,拖入更多装饰物,使场景更加丰富

4.4控制渲染顺序

如果Ruby站在一个物体的前面,Ruby应该会挡住它,如果站在后面,则会被挡住

为了实现这个功能,可以根据y轴控制渲染顺序,在Project Settiing中改变Graphics的参数

把sprite sort point设置为pivot(默认是中心),并将pivot放在一个合适的位置

以盒子为例,更改pivot到最下面,因为在那个点之下的物体会在盒子之前显示,之后的会被盒子挡住

同时设置Ruby的pivot,实现合理的显示顺序

之后把之前添加的物体都进行上述操作

五、物理系统

5.1精灵的物理系统

Ruby一直朝一个物体走时,应该要被挡住,而不是穿过去,为了实现这个效果,要为物体添加刚体和碰撞器

碰撞检测的其中一方必须要有刚体(最好是动的一方)

添加刚体,并将重力设为0,防止物体下坠,并冻结z轴的旋转,防止碰撞时Ruby旋转。

碰撞检测的双方都要有碰撞器,给盒子和Ruby都添加加碰撞器,并修改到合适的大小,这样Ruby碰到盒子就会被挡住了,之后把其他物体也进这样的操作

5.2瓦片的物理系统

单独的瓦片没有办法添加碰撞器

所以给Tilemap添加Tilemap Collider 2d和Composite Collider 2D

并给需要碰撞器的瓦片(比如池塘)的Collider Type设为gird

六、添加生命值、道具

6.1添加生命值

Ruby是有生命值的,在触碰陷阱、敌人和草莓时生命值会改变,生命值小于等于0时会重生,所以需要添加相关代码。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class RubyController : MonoBehaviour
{private Rigidbody2D rigidbody2d;//Ruby的移动速度public int speed = 5;//最大生命值public int maxHealth = 5;//Ruby的当前生命值private int currentHealth;//重生地点private Vector3 respawnPosition;//Ruby的无敌时间public float timeInvincible = 2.0f; //无敌时间常量private bool isInvincible;//计时器public float invincibleTimer;//外部可以访问Ruby的血量public int Health { get { return currentHealth; } }void Start(){rigidbody2d = GetComponent();//游戏开始时满血currentHealth = maxHealth;//重生地点为出生地respawnPosition = transform.position;//开始时无敌isInvincible = true;}// Update is called once per framevoid Update(){//获取键盘的轴向输入float horizontal = Input.GetAxis("Horizontal");float vertical = Input.GetAxis("Vertical");//创建一个position记录移动后的位置Vector2 position = transform.position;//改变x,y轴上位置position.x = position.x + speed * horizontal * Time.deltaTime;position.y = position.y + speed * vertical * Time.deltaTime;//将主角的位置置为移动后的位置rigidbody2d.MovePosition(position);if (isInvincible){invincibleTimer = invincibleTimer - Time.deltaTime;if (invincibleTimer <= 0){//无敌时间结束,Ruby又可以受到伤害了isInvincible = false;}}}//改变血量的函数public void ChangeHealth(int amount){if (amount < 0){//如果是无敌状态,直接returnif (isInvincible){return;}//受到伤害isInvincible = true;invincibleTimer = timeInvincible;}currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); //将血量currentHealth限定在0到maxHealth//Debug.Log("Ruby当前的生命值是:"+currentHealth + "/" + maxHealth);//血量小于等于0会重生if (currentHealth <= 0){Respawn();}}//重生方法private void Respawn(){ChangeHealth(maxHealth);//改变Ruby位置transform.position = respawnPosition;}
}

6.2制作血量回复道具

制作生命回复道具草莓,并添加脚本,以实现Ruby不是满血时回复一点生命值。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HealthCollectible : MonoBehaviour
{// Start is called before the first frame updateprivate void OnTriggerEnter2D(Collider2D collision){//Debug.Log("与我们发生碰撞的是:" + collision);RubyController rubyController = collision.GetComponent();//接触到的物体有RubyController组件,即接触到的物体是Rubyif (rubyController != null){if (rubyController.Health < rubyController.maxHealth)//Ruby不满血{//回复一点生命值rubyController.ChangeHealth(1);//将草莓销毁Destroy(gameObject);}}}
}

制作陷阱区域DamageZone,Ruby触碰到后生命值减1,并有一段无敌时间,防止一直扣血,无敌时间过后如果还在区域内,继续扣一点生命值。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class DamageZone : MonoBehaviour
{// Start is called before the first frame updateprivate void OnTriggerStay2D(Collider2D collision){RubyController rubyController = collision.GetComponent();if (rubyController != null)//接触到的物体有RubyController组件,即接触到的物体是Ruby{rubyController.ChangeHealth(-1);}}
}

七、添加敌人

添加一个敌人,并添加好刚体、碰撞器和脚本,编写脚本,使敌人自动来回移动,当敌人和Ruby相接触时,Ruby的生命值减少。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class EnemyController : MonoBehaviour
{public float speed = 3;private Rigidbody2D rigidbody2d;public bool vertical;//方向控制private int direction = 1;//改变移动方向的时间public float changeTime = 3;private float timer;void Start(){rigidbody2d = GetComponent();}// Update is called once per framevoid Update(){//以下都是移动timer -= Time.deltaTime;//时间减到比0小时,改变移动方向if (timer < 0){direction = -direction;//animator.SetFloat("MoveX", direction);timer = changeTime;}Vector2 position = rigidbody2d.position;if (vertical){position.y = position.y + Time.deltaTime * speed * direction;}else{position.x = position.x + Time.deltaTime * speed * direction;}rigidbody2d.MovePosition(position);}//和Ruby接触时,Ruby生命值降低private void OnCollisionEnter2D(Collision2D collision){RubyController rubyController = collision.gameObject.GetComponent();//判断接触到的是Rubyif (rubyController != null){rubyController.ChangeHealth(-1);}}
}

八、动画系统

到现在为止,Ruby和敌人移动时只是一张图片在平移,为了播放对应的动画,需要使用Unity的动画系统。

8.1敌人的动画

创建一个动画控制器,添加到敌人上。

创建好上下左右移动的动画

制作好四个方向的动画之后,拖到动画控制器中,使用混合树来控制动画切换。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class EnemyController : MonoBehaviour
{public float speed = 3;private Rigidbody2D rigidbody2d;private Animator animator;public bool vertical;//方向控制private int direction = 1;public float changeTime = 3;private float timer;void Start(){rigidbody2d = GetComponent();animator = GetComponent();}// Update is called once per framevoid Update(){//以下都是移动timer -= Time.deltaTime;if (timer < 0){//改变移动方向direction = -direction;PlayMoveAnimation();//animator.SetFloat("MoveX", direction);timer = changeTime;}Vector2 position = rigidbody2d.position;if (vertical){position.y = position.y + Time.deltaTime * speed * direction;}else{position.x = position.x + Time.deltaTime * speed * direction;}rigidbody2d.MovePosition(position);}//和Ruby接触时,Ruby生命值降低private void OnCollisionEnter2D(Collision2D collision){RubyController rubyController = collision.gameObject.GetComponent();//判断接触到的是Rubyif (rubyController != null){rubyController.ChangeHealth(-1);}}//控制移动动画的方法private void PlayMoveAnimation(){//改变参数,已实现动画的方向的变化if (vertical){animator.SetFloat("MoveX", 0);animator.SetFloat("MoveY", direction);}else{animator.SetFloat("MoveX", direction);animator.SetFloat("MoveY", 0);}}
}

8.2Ruby的动画

用同样的方法制作Ruby的动画,官方的资源中已经制作好了Ruby的控制器,动画和参数都已经设置好了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class RubyController : MonoBehaviour
{private Rigidbody2D rigidbody2d;//Ruby的移动速度public int speed = 5;//最大生命值public int maxHealth = 5;//Ruby的当前生命值private int currentHealth;//重生地点private Vector3 respawnPosition;//Ruby的无敌时间public float timeInvincible = 2.0f; //无敌时间常量private bool isInvincible;//计时器public float invincibleTimer;//外部可以访问Ruby的血量public int Health { get { return currentHealth; } }private Animator animator;//创建向量,表示方向private Vector2 lookDirection = new Vector2(1, 0);public GameObject projectilePrefab;void Start(){rigidbody2d = GetComponent();//游戏开始时满血currentHealth = maxHealth;//重生地点为出生地respawnPosition = transform.position;//开始时无敌isInvincible = true;//获取动画控制器animator = GetComponent();}// Update is called once per framevoid Update(){//获取键盘的轴向输入float horizontal = Input.GetAxis("Horizontal");float vertical = Input.GetAxis("Vertical");Vector2 move = new Vector2(horizontal, vertical);if (!Mathf.Approximately(move.x, 0) || !Mathf.Approximately(move.y, 0)){lookDirection.Set(move.x, move.y); //lookDirection=move;lookDirection.Normalize();}//控制动画的方向animator.SetFloat("Look X", lookDirection.x);animator.SetFloat("Look Y", lookDirection.y);animator.SetFloat("Speed", move.magnitude);//move的模长//创建一个position记录移动后的位置Vector2 position = transform.position;//改变x,y轴上位置//position.x = position.x + speed * horizontal * Time.deltaTime;//position.y = position.y + speed * vertical * Time.deltaTime;position = position + speed * move * Time.deltaTime;//将主角的位置置为移动后的位置rigidbody2d.MovePosition(position);if (isInvincible){invincibleTimer = invincibleTimer - Time.deltaTime;if (invincibleTimer <= 0){isInvincible = false;}}}//改变血量的函数public void ChangeHealth(int amount){if (amount < 0){//如果是无敌状态,直接returnif (isInvincible){return;}//受到伤害isInvincible = true;invincibleTimer = timeInvincible;}currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); //将血量currentHealth限定在0到maxHealth//Debug.Log("Ruby当前的生命值是:"+currentHealth + "/" + maxHealth);//血量小于等于0重生if (currentHealth <= 0){Respawn();}}private void Respawn(){ChangeHealth(maxHealth);transform.position = respawnPosition;}
}

九、发射齿轮

Ruby可以发射齿轮,击中物体会消失,如果击中的是敌人,敌人会被修好。另外,如果没有击中物体,也要让齿轮飞行一段距离后消失,否则齿轮会越来越多,消耗资源

9.1齿轮的开发

为了能够发射很多子弹,可以让Ruby发射齿轮时在Ruby面前生成一个齿轮,给它一个向前的力,如果碰到物体或飞行了一定的时间后就消失。

。首先创建一个子弹,添加刚体、碰撞器和脚本,编写脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Projectile : MonoBehaviour
{// Start is called before the first frame updateprivate Rigidbody2D rigidbody2d;void Awake() //在实例化之前运行 用start会报错 原因:在主角中实例化后获取脚本,直接跑方法,而不运行start{rigidbody2d = GetComponent();}private void Update(){if (transform.position.magnitude > 20){Destroy(gameObject);}}public void Launch(Vector2 direction, float force){rigidbody2d.AddForce(direction * force);}private void OnCollisionEnter2D(Collision2D collision){//Debug.Log("当前触碰到的物体是:" + collision.gameObject);Destroy(gameObject);}
}

RubyController也要修改,玩家按H时发射齿轮,并播放Hit动画。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class RubyController : MonoBehaviour
{private Rigidbody2D rigidbody2d;//Ruby的移动速度public int speed = 5;//最大生命值public int maxHealth = 5;//Ruby的当前生命值private int currentHealth;//重生地点private Vector3 respawnPosition;//Ruby的无敌时间public float timeInvincible = 2.0f; //无敌时间常量private bool isInvincible;//计时器public float invincibleTimer;//外部可以访问Ruby的血量public int Health { get { return currentHealth; } }private Animator animator;//创建向量,表示方向private Vector2 lookDirection = new Vector2(1, 0);public GameObject projectilePrefab;void Start(){rigidbody2d = GetComponent();//游戏开始时满血currentHealth = maxHealth;//重生地点为出生地respawnPosition = transform.position;//开始时无敌isInvincible = true;//获取动画控制器animator = GetComponent();}// Update is called once per framevoid Update(){//获取键盘的轴向输入float horizontal = Input.GetAxis("Horizontal");float vertical = Input.GetAxis("Vertical");Vector2 move = new Vector2(horizontal, vertical);if (!Mathf.Approximately(move.x, 0) || !Mathf.Approximately(move.y, 0)){lookDirection.Set(move.x, move.y); //lookDirection=move;lookDirection.Normalize();}animator.SetFloat("Look X", lookDirection.x);animator.SetFloat("Look Y", lookDirection.y);animator.SetFloat("Speed", move.magnitude);//move的模长//创建一个position记录移动后的位置Vector2 position = transform.position;//改变x,y轴上位置//position.x = position.x + speed * horizontal * Time.deltaTime;//position.y = position.y + speed * vertical * Time.deltaTime;position = position + speed * move * Time.deltaTime;//将主角的位置置为移动后的位置rigidbody2d.MovePosition(position);if (isInvincible){invincibleTimer = invincibleTimer - Time.deltaTime;if (invincibleTimer <= 0){isInvincible = false;}}//按H发射齿轮if (Input.GetKeyDown(KeyCode.H)){Launch();}}//改变血量的函数public void ChangeHealth(int amount){if (amount < 0){//如果是无敌状态,直接returnif (isInvincible){return;}//受到伤害isInvincible = true;invincibleTimer = timeInvincible;}currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); //将血量currentHealth限定在0到maxHealth//Debug.Log("Ruby当前的生命值是:"+currentHealth + "/" + maxHealth);if (currentHealth <= 0){Respawn();}}private void Launch(){GameObject projectileObject = Instantiate(projectilePrefab, rigidbody2d.position+Vector2.up*0.5f, Quaternion.identity);Projectile projectile= projectileObject.GetComponent();projectile.Launch(lookDirection,300);animator.SetTrigger("Launch");}private void Respawn(){ChangeHealth(maxHealth);transform.position = respawnPosition;}
}

当齿轮创建出来时,检测到Ruby,所以会立刻消失,为了让齿轮和Ruby不发生碰撞建检测,需要把他们放在两个不同的层级,并让这两个层级不发生碰撞检测

这样就可以正常发射齿轮了

9.2敌人被击中的效果

敌人被击中时,会被修复,不再移动和造成伤害

需要修改敌人和齿轮的脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class EnemyController : MonoBehaviour
{public float speed = 3;private Rigidbody2D rigidbody2d;private Animator animator;public bool vertical;//方向控制private int direction = 1;public float changeTime = 3;private float timer;//判断当前机器人是否故障private bool broken;void Start(){rigidbody2d = GetComponent();animator = GetComponent();//开始时机器人是损坏的broken = true;}// Update is called once per framevoid Update(){if (!broken){//如果修好,不移动return;}//以下都是移动timer -= Time.deltaTime;if (timer < 0){//改变移动方向direction = -direction;PlayMoveAnimation();//animator.SetFloat("MoveX", direction);timer = changeTime;}Vector2 position = rigidbody2d.position;if (vertical){position.y = position.y + Time.deltaTime * speed * direction;}else{position.x = position.x + Time.deltaTime * speed * direction;}rigidbody2d.MovePosition(position);}private void OnCollisionEnter2D(Collision2D collision){RubyController rubyController = collision.gameObject.GetComponent();if (rubyController != null){rubyController.ChangeHealth(-1);}}//控制移动动画的方法private void PlayMoveAnimation(){if (vertical){animator.SetFloat("MoveX", 0);animator.SetFloat("MoveY", direction);}else{animator.SetFloat("MoveX", direction);animator.SetFloat("MoveY", 0);}}//修复方法public void Fix(){broken = false;rigidbody2d.simulated = false;//播放修复好的动画animator.SetTrigger("Fixed");}
}

齿轮在检测到碰撞到的物体是敌人后,会调用敌人的Fix()方法,实现机器人的修复

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Projectile : MonoBehaviour
{// Start is called before the first frame updateprivate Rigidbody2D rigidbody2d;void Awake() //在实例化之前运行 用start会报错 原因:在主角中实例化后获取脚本,直接跑方法,而不运行start{rigidbody2d = GetComponent();}private void Update(){if (transform.position.magnitude > 20){Destroy(gameObject);}}public void Launch(Vector2 direction, float force){rigidbody2d.AddForce(direction * force);}private void OnCollisionEnter2D(Collision2D collision){//Debug.Log("当前触碰到的物体是:" + collision.gameObject);EnemyController enemyController = collision.gameObject.GetComponent();if (enemyController != null){enemyController.Fix();}Destroy(gameObject);}
}

十.镜头跟随

为了让Ruby移动时镜头能够跟随Ruby,需要添加虚拟相相机

10.1添加虚拟相机

在Package Manager中导入包

创建一个虚拟相机

让虚拟相机跟随Ruby,并添加限制器,防止视角跑出地图外

十一.完善地图并制作地图边界

添加更多物体,使地图更加丰富。并制作好边界,使相机的范围不会超过限制范围

十二、粒子系统

12.1烟雾特效

为了让机器人损坏时有冒烟的特效,添加一个粒子系统

并调整参数和图片,已实现烟雾效果

修改机器人的脚本,时机器人被修复时不播放烟雾效果

//烟雾系统
public ParticleSystem smokeEffect;//....重复代码省略public void Fix(){broken = false;rigidbody2d.simulated = false;animator.SetTrigger("Fixed");//烟雾停止smokeEffect.Stop();}

12.2击打特效

制作击打特效,在齿轮碰撞物体时产生

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Projectile : MonoBehaviour
{// Start is called before the first frame updateprivate Rigidbody2D rigidbody2d;//爆炸特效public GameObject effectParticle;void Awake() //在实例化之前运行 用start会报错 原因:在主角中实例化后获取脚本,直接跑方法,而不运行start{rigidbody2d = GetComponent();}private void Update(){if (transform.position.magnitude > 20){Destroy(gameObject);}}public void Launch(Vector2 direction, float force){rigidbody2d.AddForce(direction * force);}private void OnCollisionEnter2D(Collision2D collision){//Debug.Log("当前触碰到的物体是:" + collision.gameObject);//原地生成爆炸特效EnemyController enemyController = collision.gameObject.GetComponent();if (enemyController != null){enemyController.Fix();}Destroy(gameObject);}
}

12.3回血特效

Ruby拾取草莓时生成回血特效

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HealthCollectible : MonoBehaviour
{//回复特效public GameObject effectParticle;// Start is called before the first frame updateprivate void OnTriggerEnter2D(Collider2D collision){//Debug.Log("与我们发生碰撞的是:" + collision);RubyController rubyController = collision.GetComponent();//接触到的物体有RubyController组件,即接触到的物体是Rubyif (rubyController != null){if (rubyController.Health < rubyController.maxHealth)//Ruby不满血{//原地生成回复特效Instantiate(effectParticle, transform.position, Quaternion.identity);rubyController.ChangeHealth(1);Destroy(gameObject);}}}
}

十三.UGUI

13.1血条制作

为了显示Ruby的血量,创建一个Canvas,在里面制作出血条,放在屏幕左上角

用血量图片的伸缩表示血量的多少,需要用脚本控制血条变化

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class UIHealthBar : MonoBehaviour
{public Image mask;float originalSize;public static UIHealthBar instance { get; private set; }public int fixedNum=0;// Start is called before the first frame updatepublic void Awake(){instance = this;}void Start(){originalSize = mask.rectTransform.rect.width;}// Update is called once per framevoid Update(){}public void SetValue(float fillPercent){mask.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, originalSize * fillPercent);}
}

血条会随血量变化而变化

十四、制作NPC

游戏中需要NPC,Ruby和NPC对话后可以发射子弹,并且当Ruby修好所有机器人后NPC和Ruby的对话会改变

14.1NPC创建

拖入NPC的图片,并添加动画控制器,制作动画,添加碰撞器

14.2与NPC对话

在NPC面前创建对话框

当Ruby在NPC面前按下T时,对话框出现

先创建NPC的脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class NPCDialog : MonoBehaviour
{public GameObject dialogBox;public float displayTime=3.0f;private float timerDisplay=-1;public Text dialogText;public AudioSource audioSource;public AudioClip winSound;public bool hasPlayed=false;// Start is called before the first frame updatevoid Start(){timerDisplay = displayTime;dialogBox.SetActive(false);}// Update is called once per framevoid Update(){if (timerDisplay >= 0){timerDisplay = timerDisplay - Time.deltaTime;}else{dialogBox.SetActive(false);}}public void DisPlayDialog(){timerDisplay = displayTime;dialogBox.SetActive(true);}
}

为了检测Ruby是否在NPC面前,使用射线检测,检测到NPC时,显示对话框

在RubyController中的Update中添加if语句,如果检测到的物体是在NPC的layer中,调用NPC的脚本显示对话框,因此需要把NPC放在“NPC”的layer中

    if(Input.GetKeyDown(KeyCode.T)){//射线检测到NPCRaycastHit2D hit = Physics2D.Raycast(rigidbody2d.position + 				   Vector2.up*0.2f,lookDirection,1.5f,LayerMask.GetMask("NPC"));if(hit.collider!=null){NPCDialog npcDialog = hit.collider.GetComponent();if(npcDialog!=null){npcDialog.DisPlayDialog();}}}

十五、添加音乐和音效

15.1背景音乐

添加一个空物体,添加Audio Source并添加背景音乐,勾选loop循环播放

15.2添加音效

给Ruby挂载上音效,实现受伤、发射、走路的音效

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class RubyController : MonoBehaviour
{private Rigidbody2D rigidbody2d;public int speed = 10;//Ruby的速度public int maxHealth = 5;//最大生命值private int currentHealth;//Ruby的当前生命值//Ruby的无敌时间public float timeInvincible = 2.0f; //无敌时间常量private bool isInvincible;public float invincibleTimer;//计时器private Vector2 lookDirection = new Vector2(1, 0);private Animator animator;public GameObject projectilePrefab;//音频资源public AudioSource audioSource;public AudioSource walkAudioSource;public AudioClip playerHit;public AudioClip attackSoundClip;public AudioClip walkSound;public int Health { get { return currentHealth; }} //get只读 set{health=value;} 写private Vector3 respawnPosition;// Start is called before the first frame updatevoid Start(){rigidbody2d = GetComponent();currentHealth = maxHealth;animator = GetComponent();isInvincible = true;//int a = GetRubyHealthValue();//Debug.Log("Ruby当前的血量是:" + a);//SpriteRenderer dog = GetComponent();audioSource = GetComponent();respawnPosition = transform.position;}// Update is called once per framevoid Update(){float h=Input.GetAxis("Horizontal");float v = Input.GetAxis("Vertical");Vector2 move = new Vector2(h, v);if (!Mathf.Approximately(move.x, 0) || !Mathf.Approximately(move.y, 0)){lookDirection.Set(move.x, move.y); //lookDirection=move;lookDirection.Normalize();//如果走路音效没有在播放,播放走路音效if(!walkAudioSource.isPlaying){walkAudioSource.Play();walkAudioSource.clip = walkSound;}}else{//已经在播放则不播放walkAudioSource.Stop();}//控制动画方向animator.SetFloat("Look X",lookDirection.x);animator.SetFloat("Look Y",lookDirection.y);animator.SetFloat("Speed", move.magnitude);//move的模长Vector2 position = transform.position;//position.x = position.x + speed*h*Time.deltaTime;//position.y = position.y + speed*v*Time.deltaTime;position = position + speed * move * Time.deltaTime;rigidbody2d.MovePosition(position);if(isInvincible){invincibleTimer = invincibleTimer - Time.deltaTime;if(invincibleTimer<=0){isInvincible = false;}}if(Input.GetKeyDown(KeyCode.H)){Launch();}if(Input.GetKeyDown(KeyCode.T)){RaycastHit2D hit = Physics2D.Raycast(rigidbody2d.position + Vector2.up*0.2f,lookDirection,1.5f,LayerMask.GetMask("NPC"));if(hit.collider!=null){NPCDialog npcDialog = hit.collider.GetComponent();if(npcDialog!=null){npcDialog.DisPlayDialog();}}}}public void ChangeHealth(int amount){if(amount<0){if(isInvincible){return;}animator.SetTrigger("Hit");isInvincible = true;//播放受伤音效PlaySound(playerHit);invincibleTimer = timeInvincible;}currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); //将血量currentHealth限定在0到maxHealth//Debug.Log("Ruby当前的生命值是:"+currentHealth + "/" + maxHealth);UIHealthBar.instance.SetValue(currentHealth / (float)maxHealth);if(currentHealth<=0){Respawn();}}private void Launch(){GameObject projectileObject = Instantiate(projectilePrefab, rigidbody2d.position+Vector2.up*0.5f, Quaternion.identity);Projectile projectile= projectileObject.GetComponent();projectile.Launch(lookDirection,300);animator.SetTrigger("Launch");//发射时播放发射音频PlaySound(attackSoundClip);}//播放音效的方法public void PlaySound(AudioClip audioClip){audioSource.PlayOneShot(audioClip);}private void Respawn(){ChangeHealth(maxHealth);transform.position = respawnPosition;}
}

Ruby拾取草莓回血后,也会播放音效

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class HealthCollectible : MonoBehaviour
{public AudioClip audioClip;//回复特效public GameObject effectParticle;// Start is called before the first frame updateprivate void OnTriggerEnter2D(Collider2D collision){//Debug.Log("与我们发生碰撞的是:" + collision);RubyController rubyController = collision.GetComponent();if(rubyController!=null)//接触到的物体有RubyController组件,即接触到的物体是Ruby{if (rubyController.Health

机器人走动时、被修好时也有音效

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class EnemyController : MonoBehaviour
{public float speed=3;private Rigidbody2D rigidbody2d;public bool vertical;//方向控制private int direction = 1;public float changeTime = 3;private float timer;private Animator animator;//当前机器人是否故障private bool broken;public ParticleSystem smokeEffect;// Start is called before the first frame update//音频资源private AudioSource audioSource;public AudioClip fixSound;public AudioClip[] hitSounds;public GameObject hitEffectParticle;void Start(){rigidbody2d = GetComponent();timer = changeTime;animator = GetComponent();//animator.SetFloat("MoveX", direction);//animator.SetBool("Vertical", vertical);PlayMoveAnimation();broken = true;audioSource = GetComponent();}// Update is called once per framevoid Update(){if(!broken){//如果修好,不移动return;}//以下都是移动timer -= Time.deltaTime;if(timer<0){direction = -direction;//animator.SetFloat("MoveX", direction);PlayMoveAnimation();timer = changeTime;}Vector2 position = rigidbody2d.position;if(vertical){position.y = position.y + Time.deltaTime * speed*direction;}else{position.x = position.x + Time.deltaTime * speed*direction;}rigidbody2d.MovePosition(position);}private void OnCollisionEnter2D(Collision2D collision){RubyController rubyController = collision.gameObject.GetComponent();if(rubyController!=null){rubyController.ChangeHealth(-1);}}//控制移动动画的方法private void PlayMoveAnimation(){if (vertical){animator.SetFloat("MoveX", 0);animator.SetFloat("MoveY", direction);}else{animator.SetFloat("MoveX", direction);animator.SetFloat("MoveY", 0);}}public void Fix(){Instantiate(hitEffectParticle, transform.position, Quaternion.identity);broken = false;rigidbody2d.simulated = false;animator.SetTrigger("Fixed");smokeEffect.Stop();int randomNum = Random.Range(0, 2);//被修好时停止走路音频audioSource.Stop();audioSource.volume = 0.5f;//播放击中音效audioSource.PlayOneShot(hitSounds[randomNum]);Invoke("PlayFixSound",0.1f);}//被修好音效的方法private void PlayFixSound(){audioSource.PlayOneShot(fixSound);}}

十六、任务系统

游戏中Ruby并不是一开始就可以发射齿轮的,只有在接收了NPC的任务之后才能解锁发射齿轮。当Ruby修复好了所有机器人之后,任务完成,可以向NPC提交任务。

16.1接受任务

为了判断是都接受任务,需要一个全局变量hasTask,我们放在UIHealthBar中

//是否有任务public bool hasTask;

在NPCDialog中的DisPlayDialog()中添加语句,让Ruby在接受任务时将该值置为true

    public void DisPlayDialog(){timerDisplay = displayTime;dialogBox.SetActive(true);//接到任务UIHealthBar.instance.hasTask = true;}

修改Ruby的Lauch()方法

    private void Launch(){//没有任务直接return,无法发射齿轮if (UIHealthBar.instance.hasTask ==false){return;}GameObject projectileObject = Instantiate(projectilePrefab, rigidbody2d.position+Vector2.up*0.5f, Quaternion.identity);Projectile projectile= projectileObject.GetComponent();projectile.Launch(lookDirection,300);animator.SetTrigger("Launch");PlaySound(attackSoundClip);}

这样只有在接收到任务时才能发射齿轮了。

16.2完成任务

为了判断任务是否完成,需要添加一个全局变量,fixedNum,每修好一个机器人,fixedNum就加一,当修好所有机器人后,任务就算完成了,对话框会修改,并且播放胜利音效

    //用修好的数量判断任务是否完成public int fixedNum=0;

敌人脚本的Fix()方法,当一个机器人执行Fix()方法后,就代表有一个机器人被修好,所以fixedNum加1

public void Fix()
{//修好的机器人数量加一UIHealthBar.instance.fixedNum++;Instantiate(hitEffectParticle, transform.position, Quaternion.identity);broken = false;rigidbody2d.simulated = false;animator.SetTrigger("Fixed");smokeEffect.Stop();int randomNum = Random.Range(0, 2);audioSource.Stop();audioSource.volume = 0.5f;audioSource.PlayOneShot(hitSounds[randomNum]);Invoke("PlayFixSound",0.1f);}

NPCDialog的DisPlayDialog()方法

public void DisPlayDialog()
{timerDisplay = displayTime;dialogBox.SetActive(true);UIHealthBar.instance.hasTask = true;//所有机器人都被修好,我的场景中添加了5个机器人,所有fixedNum大鱼大于等于5时任务就完成了if(UIHealthBar.instance.fixedNum>=5){//完成任务,修改对话框dialogText.text = "哦,谢谢你Ruby,你太棒了!";if(hasPlayed==false){//播放胜利音效audioSource.PlayOneShot(winSound);hasPlayed = true;}  }
}

十七、游戏打包

游戏基本制作完成后,打包

设置好游戏图标等信息

然后在Build Settings中Build

找到exe文件运行,就可以直接游玩游戏了。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部