前几天参加了Unity的GameJam,作为第一次参加比赛的萌新,不指望能做出什么惊天动地的内容,但是希望能够从中有所收获。
现在复盘一下我在游戏项目中用到的一些技巧和Unity知识点,以后再接再励呀。
作品的地址在此:https://www.bilibili.com/video/BV1184y1q7YY/?vd_source=e99f21b5eb43aa3bce10e93c333f70d7
一个完整项目的构成要素和一些难点:
- UI系统:包括UI交互框架、场景转换等,主要是用到UGUI、SceneManagement,还可以用DOTween进行辅助来做一些额外的效果。
- 声音系统:需要弄清楚AudioSource和AudioClip之间的区别,本次项目中使用了一个贯穿始终的AudioSource来播放背景音乐。
- 主体游戏逻辑:就是下面说到的一笔画逻辑了。
- 数据体系:数据读取采用ScriptableObject,数据保存采用了PlayerPrefs。
泛型单例
本次项目中的泛型单例类代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static T instance;
public static T Instance
{
get { return instance; }
}
protected virtual void Awake()
{
if (instance != null)
Destroy(gameObject);
else
instance = (T)this;
}
protected virtual void OnDestroy()
{
if (instance == this)
{
instance = null;
}
}
public static bool IsInitialized
{
get { return instance != null; }
}
}
使用中的时候只需要继承这个单例就好,如果是跨场景的物体,可以在Awake中加上以下代码,用在需要全局存在的GameController、非常简单好用。
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(this.gameObject);
}
观察者模式
本次游戏中会涉及到四个关卡,四个关卡会有很多共性的内容,所以采用了一个跨场景存在的GameController来管理游戏逻辑,比如,记录游戏当前关卡、已经解锁的关卡数量、加载一些共性的Prefab等。
这种情况下天然很适合用观察者模式,采用多播委托来完成一对多的监听关系。(PS:del悄悄复习一下,Action和Event都是特殊的委托,可以详细看一下这个文章:https://gamedevbeginner.com/events-and-delegates-in-unity/)如果不用观察者模式来进行监听,可能我们就只能在Update中每一帧都调用了,这种方式就原始的可怕,会导致巨大的运算量。
以“猫来了,老鼠们开始跑”为例:
- 在发布者的类中新增一个委托,并且写好触发函数
- 订阅者的构造函数或者Start函数中通过“+=”进行订阅,并且写好事件处理器
- 在响应的脚本中调用触发函数
以下是代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
//这里介绍观察者模式
//例子是:猫来了,所有的老鼠开始逃跑
public class Animal
{
protected string strName;
public Animal(string name)
{
this.strName = name;
}
public virtual void Run()
{
}
}
public class Cat: Animal
{
public Action action;//发布者
public Cat(string name):base(name)
{
}
public void Coming(/*Animal mouseA, Animal mouseB, Animal mouseC*/)
{
Debug.Log(strName + "来了");
//mouseA.Run();
//mouseB.Run();
//mouseC.Run();
//在没有观察者模式的时候需要挨个调用
if (action != null)
action();//触发事件;通过多播委托完成一对多的关系
this.Run();
}
public override void Run()
{
Debug.Log(strName + "开始追");
}
}
public class Mouse: Animal
{
public Mouse(string name, Cat cat):base(name)
{
cat.action += this.Run;//订阅者
}
public override void Run()//事件处理器
{
Debug.Log( strName + "开始逃跑");
}
}
public class ObserverPattern : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Cat cat = new Cat("小野猫");
Animal mouseA = new Mouse("mouseA", cat);
Animal mouseB = new Mouse("mouseB", cat);
Animal mouseC = new Mouse("mouseC", cat);
Animal mouseD = new Mouse("mouseD", cat);
//cat.Coming(mouseA, mouseB, mouseC);
cat.Coming();
}
// Update is called once per frame
void Update()
{
}
}
ScriptableObject
在不调用数据库的情况下,可以用ScriptableObject存储少量的数据。本次项目中四个关卡的设置数据就用ScriptableObject进行保存。
创建一个SO类型的脚本,主要包含两个要素:头部的标题栏和对于ScriptableObject的继承。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "GamePlay_SO", menuName = "Game Data/GamePlay_SO")]
public class GamePlay_SO : ScriptableObject
{
[Header("游戏逻辑数据")]
public List<Conections> lineConections;
[System.Serializable]
public class Conections
{
public int from;
public int to;
}
使用PlayerPref保存游戏数据
PlayerPref是Unity自带的数据存储工具
//数据存储
PlayerPrefs.SetInt("UnlockLevel", unlockLevel);
//这里除了整数还可以存储别的类型
//数据读取
if (PlayerPrefs.HasKey("UnclockLevel"))
{
Debug.Log(PlayerPrefs.GetInt("UnlockLevel"));
}
DOTween
一个简单易用的动画插件,用法如下:
//头部引用
using DG.Tweening;
//一个旋转的使用案例
Tweener t = LevelGameManager.Instance.resetGame.gameObject.transform.DORotate(new Vector3(0, 0, 270), 2.0f);
一些编辑器中容易混淆的点
图片格式问题
- Texture2D:可以赋值给Material,可以作为游戏的Icon;
- Sprite:通过图集Slice出来的都是这种格式,不可以转换成其他两种;这种格式不能作为Material的albedo原图;
- Image:UI中的概念,Image有OverrideSprite的属性,但是Sprite没有。
图层遮挡关系
主要的概念有Sorting Layer、Order In Layer,两者是递进关系,Order In Layer表示在Layer里面的顺序。
一笔画的逻辑
纯纯自己摸索出来的逻辑写法,可能不是最好的,但是确实我目前能想出来的。
场景生成:需要去相应的场景摆放好,然后读取ScriptableObject中每一关的游戏数据,Instantitate一个LinePrefab自动生成连线。
通关的逻辑判断:玩家操作之后新生成的线的数量和ScriptableObject中的数据一样。
暂时就是这些了,虽然自己很菜,但是还是要日拱一卒,加油加油!