我如何用Unity3D实现一个Galgame框架(一)

Galgame我很早就陆陆续续的接触过,但大都是别人推荐或名声很大、梗很多的,比如催泪的有clannad,狗血的有School Days,还有胃疼的白学。但自己对Galgame和AVG真正产生兴趣是从EVER17开始的,被最后一章解谜篇震撼到了,通关后在电脑前呆坐了几分钟,久久不能忘怀。然后就爆肝把KID社三部曲一下全推完,再接下来是5pb的石头门,InnocentGrey的壳女虚女…逐渐感受到了这种在市场上处于劣势的游戏类型的魅力。

我如何用Unity3D实现一个Galgame框架(一)_第1张图片
(ever17,震撼而动人的故事)
_

我如何用Unity3D实现一个Galgame框架(一)_第2张图片
(壳之少女,通关后有一种无法释怀,意犹未尽的感觉)

这个Galgame框架我整个大一下学期一直都在酝酿,可惜当时的我就是一个麻瓜,什么东西都不会,只有各种空的目标和想法,都是虚的。现在,我终于掌握了一些C#和Unity的技能,虽说还是很生疏,许多深层的东西都还没碰到,但终于可以开始一点点实现自己以前脑子里的东西了,感谢自己保有的那么一点自律,在暑假玩游戏之余学到了点东西。

一.脚本是必要的

我之前也发过用Unity实现gal的尝试,企图不用控件完全靠GUI代码来作显示,以及定义Dialogue类来装一个对话场景等等,结果在存档问题上走到了死胡同。(以前的文章还是留着,方便对比)向一些B站的大佬们请教了一番,我明白了这类游戏设计中脚本的重要。在GALGAME中,脚本回归了其最初的定义—剧本。它需要被用来指导游戏的进行,即文字,背景,立绘,音效,bgm等的显示,大多数时候还需要指示选择支,实现多条线的剧情。来看一看书上的一些例子:
(源自佐佐木智广《游戏剧本怎么写》)
我如何用Unity3D实现一个Galgame框架(一)_第3张图片
我如何用Unity3D实现一个Galgame框架(一)_第4张图片

(一) 脚本设计

脚本如此重要,我们先来设计一套泛用Galgame脚本语言。(仅作参考,可以根据需求设计适合自己的),脚本是需要传进游戏解析的,便于解析,最好设定分隔符来把每一行内容丢进数组。我这样设计:
1.剧情对话脚本Dialogue(用标识符为D)

D| 说话人名字| 说话内容 | (语音)(还没实现到这种地步) 

2.命令脚本Command (标识符为C)

格式:C| 命令 |...| 相关参数 |...|
具体的命令:
C|setbackground|背景名 (或路径)   //设定目前的背景
C|setbgm|bgm名 (或路径)//当前bgm
C|stopbgm //停止bgm
C|setcharacter|立绘名|绘制位置(我设的left,center和right)//绘制立绘
C|erasecharacter|立绘位置 //隐藏某位置的立绘
之后还会有更多

3.注释脚本Note (标识符为*)

*| 注释内容     //用于写注释,游戏不会去解析

4 .选择支脚本Selection(本回不实现)

S|按钮1文字|按钮1跳转行|按钮2|按钮2跳转行....

大概就是这样,怎么舒服怎么来就行了
随便写了一个测试用的脚本:

*|格式:本行标识 语句
*|普通脚本:D|名字|对话
*|命令脚本:C|setbackground|background(路径)|0 
*|          C|setbgm|bgm(名字)| 播放bgm|0
*|          C|setcharacter|立绘名|绘制位置(left,center,right)
*|        C|erasecharacter|position|.|.
*|        C|stopbgm|.|.|.
*|注释脚本:*在最前,不解析
*|选择脚本:   S|按钮1文字|按钮1跳转行|按钮2文字|按钮2跳转行....
C|setbackground|street1
C|setcharacter|Girl1|center
D|Me|你好,这是一个普通脚本的测试 
D|Me|这是下一句话,怎么样,解析成功了吗
C|setbgm|Bgm1
D|少女|这是一座很普通的小镇噢!跟我来,我来带你参观吧! 
C|erasecharacter|all
D|我|就这样跟着少女在小镇中转了一个多小时,所见之物大都是司空见惯的,没有什么特别的感受
C|setbackground|Black
D|我|这种感觉没持续多久......直到......
D|我|走进了那一间房子......
C|setbackground|room1

(二)脚本解析

设计了一套语法后,接下来我们要让游戏按行把它们解析出来:
首先,我们需要读入txt文件,然后把它处理成数组交给其他object处理。定义一个FileHandler脚本来读取,一个TextParser脚本来解析,这种类的定义我们最好都附带一个全局对象instance,方便其他脚本来用它。先来处理FileHandler:

//initialization
public static FileHandler FileHandler_Instance;//static object 
public string Director_Path; 
// on start:
 FileHandler_Instance = this;
 Director_Path = Application.streamingAssetsPath;//find streaming assets' path

要处理的文件都放在streamingAssets文件夹中,这个文件夹Unity是认得到的,通过Application.streamingAssetsPath来获取。然后是用ReadAllLines返回一个数组,交给TextParser处理。

public string[] ReadTxtFile(string filename)
    {
        string Path = Director_Path;
        //Get the file
        Path = Director_Path + "/" + filename;//definate path
        //Debug.Log("readfile:" + Path);
        //debug to test
        string[] AllText = File.ReadAllLines(Path);//split by lines
        return AllText;
    }

然后来写Parser,算是全部脚本中最核心的地方了,因为后面还会定义更多东西,所以这个parser会经常更新。(LineIndex用来记录脚本解析到了第几行)

//objects
//static:
    public static TextParser TextParser_Instance;
    //public:
    public string[] TextLines;//get returned string[]
    public string[] TempScript;
    public string TextFileName;//
    public int LineIndex;//line
//on awake: 
    TextParser_Instance = this;//get instance

定义和初始化部分目前是这样,然后要获得字符串数组

public void GetText(string name,int index)
    {
        Debug.Log("Start reading FileText");
        this.TextFileName = name;
        this.LineIndex = 0;
        TextLines = FileHandler.FileHandler_Instance.ReadTxtFile(TextFileName + ".txt");
        //Get the Text
        ParsingText(0);//start parsing the first line
    }

我们获取到的每一行,用split将其分为几段,装进数组,先判断数组首元素的标识符来确定其类型,然后进行相关操作,相关函数后面来陆续添加,先用注释表示清楚。

public void ParsingText(int num)
    {
        TempScript = TextLines[num].Split('|');
        //start parsing:
        if(TempScript[0]=="*")
        {
            //the line is a note line
            //(skip this )
            LineIndex++;
            return;
        }
        if(TempScript[0]=="C")
        {
            //the line is a command line
            if(TempScript[1]== "setbackground")
            {
                //Background switch process
                // (Set Background Method)
            }
            else if(TempScript[1]== "setbgm")
            {
                //Set current BGM
                //(set BGM Method)
            }
            else if(TempScript[1]== "setcharacter")
            {
                if (TempScript[2] != "null")
                {
                   //(Set Character Method)
                }
            }
            else if(TempScript[1]== "erasecharacter")
            {
                 //(erase Character Method)
            }
            else if (TempScript[1] == "stopbgm")
            {
                //(stop BGM Method)
            }
            ParsingText(num + 1);
            LineIndex++;
            return;
        }//process command line
        if(TempScript[1]=="S")
        {
            //Selection branch line
            return;
        }//selection end
        //process a Dialogue line
        //(Dialogue play Method)

把脚本都挂到空GameObject上,可以检查一下文本传入成功没有。
我如何用Unity3D实现一个Galgame框架(一)_第5张图片
(传入成功的样子)

然后开始编写UI相关的东西了

二. 开始界面UI以及场景转换

创建UI的画布(Canvas),然后附上相关的按钮,窗口。
我如何用Unity3D实现一个Galgame框架(一)_第6张图片
我们写一个脚本来控制所有的按钮操作(让按钮调用脚本中的方法ButtonClickEvent),用了很多setActive和Object.Find的方法,可是在Active为false(即物体未被激活)时,Find是找不到它的,该咋办?解决方法是令创建一个对象和脚本,把所有要用到的对象挂上去就找的到了.(ps:用到了SceneManagement和UI相关的控件,记得using UnityEngine.UI和UnityEngine.SceneManagement):

//Start_Menu Manager (用来挂对象)
public class StartMenu_Manager : MonoBehaviour {
    public GameObject InfoPanel;
    public GameObject Button_Start;
    public GameObject Button_Exit;
    public GameObject Button_About;
    public GameObject Effect;
   }
public class StartScene_Buttons : MonoBehaviour {
    //public
    public GameObject ChangeSceneEffect1;//to delay scene change
    //private
    private float AlphaSign = 0;//to display effects
 // Use this for initialization
 void Start () {
        ChangeSceneEffect1 = GameObject.Find("StartMenu_GUI_Manager").GetComponent<StartMenu_Manager>().Effect;
    }
 public void ButtonClickEvent()
    {
        //Debug.Log("clicked");
        //Debug.Log(gameObject.name); (used for test)
        switch(gameObject.name)
        {
            case "BTN_StartGame":
                ChangeSceneEffect1.SetActive(true);
                GameObject.Find("Menu_Particle").SetActive(false);
                ChangeSceneEffect1.GetComponent<Image>().color = new Color(0, 0, 0, 0);
                StartCoroutine("SwitchScene");
                break;
            case "BTN_About":
                //Display Info Window
                GameObject.Find("StartMenu_GUI_Manager").GetComponent<StartMenu_Manager>().InfoPanel.SetActive(true);
                //Set other Buttons's Active to false 
                GameObject.Find("StartMenu_GUI_Manager").GetComponent<StartMenu_Manager>().Button_Start.SetActive(false);
                GameObject.Find("StartMenu_GUI_Manager").GetComponent<StartMenu_Manager>().Button_Exit.SetActive(false);
                GameObject.Find("StartMenu_GUI_Manager").GetComponent<StartMenu_Manager>().Button_About.SetActive(false);
                break;
            case "BTN_ExitGame":
                Application.Quit();
                break;
            case "BTN_BackToMenu":
                GameObject.Find("StartMenu_GUI_Manager").GetComponent<StartMenu_Manager>().Button_Start.SetActive(true);
                GameObject.Find("StartMenu_GUI_Manager").GetComponent<StartMenu_Manager>().Button_Exit.SetActive(true);
                GameObject.Find("StartMenu_GUI_Manager").GetComponent<StartMenu_Manager>().Button_About.SetActive(true);
                GameObject.Find("StartMenu_GUI_Manager").GetComponent<StartMenu_Manager>().InfoPanel.SetActive(false);
                break;
            default:
                break;
        }
    }

这个按钮脚本需要每一个按钮都挂一个,这样脚本就可以判断是谁触发了这个方法。
我如何用Unity3D实现一个Galgame框架(一)_第7张图片
来写一个协程加一点场景转换时的渐变过渡,不然太生硬了

 IEnumerator SwitchScene()
    {
        yield return new WaitForSeconds(0.01f);
        while(AlphaSign<1)
        {
            yield return new WaitForSeconds(0.02f);
            AlphaSign += 0.01f;
            ChangeSceneEffect1.GetComponent<Image>().color = new Color(0, 0, 0, AlphaSign);
        }
        SceneManager.LoadScene(1);
    }

还可以来一些骚操作,比如加点粒子效果,将Canvas改为camera来render,然后在相机前面加一个粒子系统,稍作调整效果就出来了。
(测试主菜单)
我如何用Unity3D实现一个Galgame框架(一)_第8张图片
(测试窗口)
我如何用Unity3D实现一个Galgame框架(一)_第9张图片
好像要讲的有点多,今天就先到这里吧,目前已经把基本的命令操作都实现了,正在搞存档的管理,还得学JSON,慢慢来吧

未完待续

你可能感兴趣的:(Unity)