如何制作一个Unity2D闯关游戏(三)

如何制作一个Unity2D闯关游戏(三)

回顾上一篇

上一篇我们制作了Player的绝大部分动画,使我们的Player可以真正意义上的动起来,又添加了背景移动,使我们的背景可以一直循环移动,从而使得整个游戏世界更加真实。

开始第三天的制作

在完成了前两篇的内容之后,Player方面的内容我们已经大致完成,接下来我们将开始着重于关卡方面的制作。

当我们浏览完整个Sunny Land素材之后,会发现一个问题,这个素材包只有一个场景包含了关卡的场景内容,但是有许多场景内容的素材,那我们该如何制作属于自己独特的场景呢?这时候我们需要用到Unity内的一个工具来帮助我们完成自定义场景的搭建。

Tilemap的创建

在Unity的选项卡中依次选择 Windows -> 2D -> Tile Palette,打开平铺调色板窗口。然后再如下目录找到 SunnyLandTiles.png,“Assets\2D Platformer Assets\Graphics\Level Art\Tileset”,点击 Create New Palette,然后将其拖入 Tile Palette 面板,并保存在合适位置。

创建完成之后,然后我们在依次点击Unity选项卡 GameObject -> 2DObject -> Tilemap -> Rectangular创建一个Gird,然后我们就会发现Scene窗口中就出现了许多格子,现在我们就可以在 Tile Palette 窗口中选择画笔开始绘制场景了。

给Tilemap添加Collider

现在我们在场景中创建好的内容是不具有碰撞的,玩家无法在上面行走,这时候需要我们给Tilemap添加上碰撞体。我们给Tilemap添加上 Tilemap Collider 2D,Rigidbody2D 组件,并且将Rigidbody 2D的 **Body Type **设置为 Kinematic,这样场景就具有了碰撞并且不会受物理效果影响。

但是这时候的碰撞体过多,会造成性能下降,所以这时候我们需要再添加一个 Composite Collider 2D 来解决这个问题,将其的 Geometry Type 设置为 Polygons

这样场景的碰撞体就从P1变到了P2。

最后要说的一点就是,有些场景元素我们并不需要它具有碰撞,所以我们要对其进行一定的设置。

我们需要将它的 Collider Type 设置为 None

附上一张说明图,Unity默认给的类型是 Sprite

以上,自定义场景部分的内容就完成了,我们可以自由的绘制场景中的内容了,创建多个独特的关卡。

同时我们可以将 Player,Background,Main Camera 这三个最基础的元素制作成预设体,方便在多个场景内使用。

创建Spike

场景搭建完成了,我们需要给场景内添加一些尖刺,来增加游戏难度。在素材的 Level Mechanics 文件夹底下找到 Spikes,将它放入场景中,给它添加 Box Collider 2D,将 is Trigger勾上,并将碰撞体设置为图示大小。

然后在创建一个名为 SpikesDamagePlayer 的脚本,用来控制尖刺对玩家造成的伤害。

这个脚本比较简单,直接上代码。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpikesDamagePlayer : MonoBehaviour
{
    //当玩家碰到尖刺时对玩家造成伤害
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (!other.CompareTag("Player")) return;
        
        PlayerHealthController.instance.DealDamage(1);
    }
}

当然,我们上面写到的 PlayerHealthController.instance.DealDamage(1); 这个 PlayerHealthController 脚本我们还没有编写,接下来就来编写这一脚本。

玩家血量系统

上面我们写了尖刺对玩家造成伤害的的脚本,接下来我们来写玩家血量系统的脚本。

首先,我们创建一个叫 PlayerHealthController 的脚本,挂载到Player上。玩家血量系统这个脚本肯定会被多个脚本调用到,所以我们在脚本开头就将它定义为静态变量。

//这样在别的脚本我们就可以直接通过PlayerHealthController.instance.函数的方法来调用。
public static PlayerHealthController instance;

//并且在Awake函数中赋值
private void Awake()
{
	instance = this;
}

然后我们需要两个变量来表示玩家当前血量和最大血量。

public int currentHealth;//当前血量
public int maxHealth=6;//最大血量

//在start函数中将最大生命值赋给当前生命值
void Start()
{
	currentHealth = maxHealth;
}

然后我们就来编写上面用到过得DealDamage函数。

//扣血函数
public void DealDamage(int damage)//需要一个输入值,为当前扣的血量
{
    currentHealth -= damage;
    if (currentHealth <= 0)//血量小于0,则将玩家隐藏
    {
        currentHealth = 0;
        gameObject.SetActive((false));
    }
}

这样一个玩家扣血脚本就写完了,因为后续还要添加新的,所以先不上完整代码。

此处

血量UI显示系统

上面写完了玩家血量系统,那么我们需要UI来将血量信息反馈给玩家,这时候我们需要再场景中创建一个 Canvas,然后将 Canvas 按图示设置。

然后将 Game 窗口的分辨率设置为 Full HD(1920x1080),做一个最初版本的,后期再去适配其他分辨率。

我们创建一个名为 UIController 的脚本,并将其挂载到 Canvas 上。

首先我们同血量系统一样,UI系统也会被多个脚本调用,所以我们也将它定义为静态变量。

public static UIController instance;

private void Awake()
{
    instance = this;
}

然后我们需要一个血量的最初模板,所以我们需要一个血量预设体。右键Canvas, Canvas -> UI -> Image,创建一个Image,将他的 Source Image 设置为素材中血量的任意一个,点击 Set Nactive Size,然后将其制成预设体。

//定义一个Image变量来储存刚刚的预设体
public Image heartPrefab;

//开一个Image数组,用来储存血量的Image
private Image[] heart = new Image[76];//这个76可以随意

//获取满心,半心和空心的图像
public Sprite fullHeart, emptyHeart,halfHeart;

//爱心当前的位置
private Vector2 heartCurrentPosition;

//爱心开始的位置,后续适配其他分辨率的时候就修改这个值
private readonly Vector2 heartStartPosition=new Vector2(-900f, 490f);

//同一行不同列的爱心的间隔
private readonly Vector2 delColumnPos = new Vector2(100f, 0);

//不同行同一列的爱心的间隔
private readonly Vector2 delRowPos = new Vector2(0, -90f);

然后我们在Start函数里编写血量UI的初始化代码。

private void Start()
{
    //获取
    heartCurrentPosition = heartStartPosition;
    
    //初始化血量
    if (PlayerHealthController.instance != null)
    {
        //写一个For循环来生成爱心
        for (int i = 0; i < PlayerHealthController.instance.maxHealth/2; i++)//因为有半心的存在,所以需要最大值/2
        {
            //生成预设体
            heart[i] = Instantiate(heartPrefab);
            
            //设置预设体的父级关系,让他显示在Canvas上
            heart[i].transform.SetParent(transform);
            
            //获取RectTransform组件,并设置爱心在画布上的位置
            var rect = heart[i].GetComponent<RectTransform>();
            rect.anchoredPosition = heartCurrentPosition;
            
            //血量位置修改
            if ((i + 1) % 19 == 0)//我设置的大小在第19这里换行,后续适配分辨率也修改这个值即可
            {
                heartCurrentPosition += delRowPos;
                heartCurrentPosition.x = heartStartPosition.x;
            }
            else//换列
            {
                heartCurrentPosition += delColumnPos;
            }

            //然后将其设置为满血
            heart[i].sprite = fullHeart;
        }
    }
}

血量初始化完成之后,就要写每次加血或者扣血的UI更新函数了。

//更新血量UI
public void UpdateHeartDisplay()
{
    //获取玩家当前血量
    var cnt = PlayerHealthController.instance.currentHealth;
	
    //遍历满血的爱心
    for(int i=0;i<=cnt/2-1;i++)
    {
        heart[i].sprite = fullHeart;
    }
	
    //遍历空的爱心
    for(int i=PlayerHealthController.instance.maxHealth/2-1;i>=cnt/2+1;i--)
    {
        heart[i].sprite = emptyHeart;
    }

    //设置半心或者空心
    if(cnt!=PlayerHealthController.instance.maxHealth)heart[cnt / 2].sprite = cnt % 2 == 1 ? halfHeart : emptyHeart;
}

UI更新脚本写完之后,我们需要让玩家扣血之后实时更新,所以在刚才的扣血脚本中添加一句代码。

UIController.instance.UpdateHeartDisplay();

最后上三个脚本的完整代码

SpikesDamagePlayer

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpikesDamagePlayer : MonoBehaviour
{
    //对玩家造成伤害
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (!other.CompareTag("Player")) return;
        
        PlayerHealthController.instance.DealDamage(1);
    }
}

PlayerHealthController

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerHealthController : MonoBehaviour
{
    public static PlayerHealthController instance;
    
    public int currentHealth;
    public int maxHealth=6;

    private void Awake()
    {
        instance = this;
    }

    void Start()
    {
        currentHealth = maxHealth;
    }


    //玩家扣血
    public void DealDamage(int damage)
    {
        currentHealth -= damage;
        UIController.instance.UpdateHeartDisplay();
        if (currentHealth <= 0)
        {
            currentHealth = 0;

            gameObject.SetActive((false));
        }
    }
}

UIController

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UIController : MonoBehaviour
{
    public static UIController instance;
    public Image heartPrefab;
    private Image[] heart = new Image[76];
    public Sprite fullHeart, emptyHeart,halfHeart;
    
    private Vector2 heartCurrentPosition;
    private readonly Vector2 heartStartPosition=new Vector2(-900f, 490f);
    private readonly Vector2 delColumnPos = new Vector2(100f, 0);
    private readonly Vector2 delRowPos = new Vector2(0, -90f);
    
    private void Awake()
    {
        instance = this;
    }

    private void Start()
    {
        heartCurrentPosition = heartStartPosition;
        //初始化血量
        if (PlayerHealthController.instance != null)
        {
            for (int i = 0; i < PlayerHealthController.instance.maxHealth/2; i++)
            {
                heart[i] = Instantiate(heartPrefab);
                heart[i].transform.SetParent(transform);
                var rect = heart[i].GetComponent<RectTransform>();
                rect.anchoredPosition = heartCurrentPosition;
                //血量位置修改
                if ((i + 1) % 19 == 0)
                {
                    heartCurrentPosition += delRowPos;
                    heartCurrentPosition.x = heartStartPosition.x;
                }
                else
                {
                    heartCurrentPosition += delColumnPos;
                }

                heart[i].sprite = fullHeart;
            }
        }
    }
    
    
    //更新血量UI
    public void UpdateHeartDisplay()
    {
        var cnt = PlayerHealthController.instance.currentHealth;
        
        for(int i=0;i<=cnt/2-1;i++)
        {
            heart[i].sprite = fullHeart;
        }
        
        for(int i=PlayerHealthController.instance.maxHealth/2-1;i>=cnt/2+1;i--)
        {
            heart[i].sprite = emptyHeart;
        }
        
        if(cnt!=PlayerHealthController.instance.maxHealth)heart[cnt / 2].sprite = cnt % 2 == 1 ? halfHeart : emptyHeart;
    }

}

总结

本篇主要添加了自定义场景的制作,Spike尖刺系统,玩家血量系统和血量UI显示系统。有问题欢迎大家在评论区提出,谢谢!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值