Unity快速上手系列之番外篇:《2D横版跑酷》

作者:四五二十
写在前面
未尝试做过跑酷游戏,大约相当于没学过游戏开发。

玩笑至此,想传达的意思大家想必也清楚。跑酷游戏普遍非常简单,但简单并不代表简陋,跑酷是最能体现“麻雀虽小五脏俱全”的游戏类型。在当中开发者可以充分展示自己的设计才能,无论是奇思妙想的关卡设计还是灵光一现的功能创意。这样必然能获取到充足而高频的正反馈——而这一切都是建立在这样一个门槛较低的游戏体量上的。
因此对于初学者来说,没有比它更合适练手的类型了,这也是我专门写这样一篇番外篇的目的。
闲话少叙,直接开干。
创建地形
地面
标配的地面和天花板自不必说,跟着自己的喜好设计即可。
而移动地形则能稍微增加一点变数。以垂直升降为例,在自己设定的移动范围内,地形最低位置就向上移动,反之亦然。把脚本挂载到对应地形上即可:
void Update()
{
if(transform.position.y > 0)
{
IsChange = false;
}
if(transform.position.y < -6)
{
IsChange = true;
}
if(IsChange == false)
{
//在世界坐标向下移动
transform.Translate(0, -4 * Time.deltaTime, 0, Space.World);
}
else
{
//在世界坐标向上移动
transform.Translate(0, 4 * Time.deltaTime, 0, Space.World);
}
}
水平移动同理。
背景
以下图为例,一张普通的砖墙背景:

为了展示出向前跑的效果,需要让图片每一帧向后移动。
transform.position = new Vector3(transform.position.x-0.03f, transform.position.y, transform.position.z);
顺便说一句,在Unity2018版本中可以使用纹理偏移的方法来达到类似的效果,这样就不用单独移动图片了:
public float scrollSpeed = 0.5f;
public Renderer = rend;
//Use this for initialization
void Start()
{
rend = GetComponent<Renderer>();
}
//Update is called once per frame
void Update()
{
float offset = Time.time * scrollSpeed;
rend.material.mainTextureOffset = new Vector2(offset, 0); //纹理偏移
}
与之对应的就不再是创建一个精灵,而是创建Quad:

角色
角色图片
把图片素材放在精灵上,并加上刚体和碰撞盒子:

强调一下,网上的图片素材一定只能供自己学习,一定不能用作商业用途!
角色动作
1、移动
我们给角色一个速度,使得游戏一开始角色就向右移动。
myRigidbody.transform.Translate(speed * Time.deltaTime, 0, 0);
2、跳跃
按下空格键给它一个向上的力,使得角色可以向上跳跃。有两个判断:只有当检测到玩家在地面上,或者在天花板上时,才能跳跃。在空中不能跳跃。当然,通过稍微的逻辑修改,还可以让角色实现二段跳功能。
//按下空格键可以使方块跳跃
if (Input.GetKeyDown(KeyCode.Space))
{
if (Physics2D.Raycast(transform.position, Vector2.down,hight, LayerMask.GetMask("ground")))
{
myRigidbody.AddForce(Vector3.up * upspeed, ForceMode2D.Impulse); //给它一个向上的力
}
if (Physics2D.Raycast(transform.position, Vector2.down, hight, LayerMask.GetMask("ceiling")))
{
myRigidbody.AddForce(Vector3.up * upspeed, ForceMode2D.Impulse); //给它一个向上的力
}
}
3、放缩
在这个示例里,我给予了玩家一个放缩的特性,通过灵活地改变体型的大小来通过障碍物。根据体型不同,跳跃能力、奔跑速度会发生相应的变化。
if (transform.localScale.x>0.3)
{
//按下A键可以缩小方块
if (Input.GetKey(KeyCode.A))
{
transform.localScale = new Vector3(transform.localScale.x - 0.01f, transform.localScale.y - 0.01f, transform.localScale.z - 0.01f);
speed = speed + 0.05f;
upspeed = upspeed + 0.05f;
}
}
if(transform.localScale.x <=1)
{
//按下D键可以变大方块
if (Input.GetKey(KeyCode.D))
{
//检测到方块上面是天花板则不能变大
if (Physics2D.Raycast(transform.position, Vector2.up, 0.5f*hight, LayerMask.GetMask("ceiling")))
{
}
else
{
transform.localScale = new Vector3(transform.localScale.x + 0.01f, transform.localScale.y + 0.01f, transform.localScale.z + 0.01f);
speed = speed - 0.05f;
upspeed = upspeed - 0.05f;
}
}
效果如图:

摄像机
这一部分的处理较为简单,只需要让镜头跟随玩家移动即可。
//让相机跟着玩家移动
transform.position = new Vector3(player.transform.position.x + 5, 0 , transform.position.z);
金币
金币是场景中散落的收集要素,就跟《超级马里奥》中的金币一样。
图片资源随便用一个:

我们让金币带上一个旋转的样式:
transform.Rotate(Vector3.up * 4, Space.World); //原地旋转
效果如下图所示:

根据收集逻辑,场景中的金币一旦被玩家所触碰到,就会自动移动至UI中金币数量位置。
金币的移动函数如下:
//金币向目标点移动
public void CoinMove()
{
//UI坐标转换成世界坐标
UIcoin = Camera.main.ScreenToWorldPoint(AllCoin.transform.position);
//当前物体向这某一个物体移动
transform.position = Vector3.MoveTowards(transform.position, UIcoin + Vector3.forward, 25 * Time.deltaTime);
//两个坐标相减是方向,用sqrMagnitude获取方向的距离
if ((transform.position - (UIcoin + Vector3.forward)).sqrMagnitude < 0.1f)
{
player.GetComponent<PlayerMove>().money++;
player.GetComponent<PlayerMove>().SetMoney();
Destroy(gameObject);
}
}
当然,要触发这个移动函数,还需要增加一个检测碰撞的过程,在过程中调用移动函数。不要忘了给金币添加碰撞盒子。
void Update ()
{
transform.Rotate(Vector3.up * 4, Space.World); //原地旋转
if (IsRun == true)
{
CoinMove();
}
}
public void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.CompareTag("Player"))
{
IsRun = true;
}
}
在角色脚本中获取该数量text。
void Start ()
{
myRigidbody = this.GetComponent<Rigidbody2D>();
money = 0;
SetMoney();
Hp.gameObject.SetActive(false);
}
public void SetMoney() //改变金币数量
{
MoneyText.text = money.ToString();
}
玩家碰到金币时,金币在移动至相应位置的同时,数量+1,并销毁自己。
player.GetComponent<PlayerMove>().money++;
player.GetComponent<PlayerMove>().SetMoney();
Destroy(gameObject);
机关
角色一旦碰到场景中的各种机关,就宣告game over。机关分为固定机关和移动机关两种。
移动机关当检测到角色靠近时,就自动向下掉落,碰到地板后停止运动,在掉落过程中如果碰到玩家则game over。
void Update ()
{
if (isRun==true)
{
//玩家靠近绝对值距离小于4时火焰下落
if (Mathf.Abs(player.transform.position.x-transform.position.x)<4)
{
transform.Translate(0, -9 * Time.deltaTime, 0, Space.World);
}
}
}
public void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.CompareTag("ground"))
{
isRun = false;
}
磁铁功能
很多跑酷游戏里都会有“磁铁”这样的powerup道具,吃到后,会在一定时间内让场景中的金币自动被吸附到玩家身上。
碰撞盒
要实现这样的功能,我们首先需要给磁铁加一个可任意调整的碰撞盒。

随意改变一下形状,能碰到前方的金币即可。

拾取金币
public void OnTriggerEnter2D(Collider2D coll)
{
if(coll.gameObject.CompareTag("coin"))
{
coll.GetComponent<Coin>().IsRun = true;
}
}
碰撞盒碰到金币的盒子时,把金币的IsRun改为True,此时金币就会自动跑向目标点,达到磁铁吸金的效果。
跟随角色
角色碰到磁铁后,为了让吸金效果跟随角色,需要让磁铁本身也跟随角色一起移动。
if (coll.gameObject.CompareTag("Player"))
{
IsFollow = true;
}
void Update ()
{
if (IsFollow == true)
{
//跟着玩家移动
transform.position = new Vector3(player.transform.position.x - 0.5f, player.transform.position.y + 1, transform.position.z);
}
}
时间条
我们首先创建两张图片,分别为白色和绿色的,绿色图片在白色图片之上,两张大小一样。

找到面板上的绿色时间条,然后以帧为单位逐渐缩小尺寸。在减少为0的时候销毁磁铁和时间条,意味着磁铁失效。
//找到玩家
public GameObject player;
//找到血量条
public GameObject MagnetHp;
//找到HP图片
public Image HpPhoto;
//HP数值
public float Hp;
//是否移动
public bool IsFollow = false;
void Update ()
{
if (IsFollow==true)
{
//改变Rotation
transform.eulerAngles = new Vector3(0,0,0);
transform.localScale = new Vector3(0.17f, 0.17f, transform.localScale.z);
transform.position = new Vector3(player.transform.position.x-0.5f, player.transform.position.y+1, transform.position.z);
player.GetComponent<PlayerMove>().Hp.gameObject.SetActive(true);
Hp = Hp - 0.002f;
if(Hp<0)
{
Hp = 0;
Destroy(transform.parent.gameObject); //销毁磁铁
Destroy(MagnetHp); //销毁血条
}
//实时更新面板上的HP数值
HpPhoto.rectTransform.localScale = new Vector3(Hp, HpPhoto.rectTransform.localScale.y, HpPhoto.rectTransform.localScale.z);
}
}
切换属性
这是在本篇示例中另外添加的一个特性:属性切换。
场景中有时会出现类似于水墙的障碍,玩家需要切换角色的属性后才能通过。
水属性图片:

当角色触碰到上图时,切换角色外形为如下:

player.GetComponent<SpriteRenderer>().sprite = tupian;
当检测到玩家属性已切换时,将水墙的碰撞盒变为可穿越即可。
rainbowwall.GetComponent<BoxCollider2D>().isTrigger = true;
游戏结束
goal标志
当玩家到达goal标志时,判定玩家闯关成功。

反之,玩家碰到机关、摔下悬崖或是未能穿过水墙时,判定游戏失败。

That's it。实际运行的效果如下:

完整的工程如下:https://github.com/wushupei/RunGame
这样一个简单的游戏却作用多多,不光可以应对类似于课程设计之类的学习任务,还足以作为开发者的入门第一次实践。希望能藉由此文,让尽量多的盆友们踏上游戏开发令人激动的旅程中去。
最后想系统学习游戏开发的童鞋,欢迎访问 http://levelpp.com/
游戏开发搅基QQ群:869551769
微信公众号:皮皮关