unity编程实践 牧师与魔鬼

编程实践

阅读以下游戏脚本

Priests and Devils

Priests and Devils is a puzzle game in which you will help the Priests and Devils to cross the river within the time limit. There are 3 priests and 3 devils at one side of the river. They all want to get to the other side of this river, but there is only one boat and this boat can only carry two persons each time. And there must be one person steering the boat from one side to the other side. In the flash game, you can click on them to move them and click the go button to move the boat to the other direction. If the priests are out numbered by the devils on either side of the river, they get killed and the game is over. You can try it in many > ways. Keep all priests alive! Good luck!

程序需要满足的要求:

play the game ( http://www.flash-game.net/game/2535/priests-and-devils.html )
列出游戏中提及的事物(Objects)
用表格列出玩家动作表(规则表),注意,动作越少越好
请将游戏中对象做成预制
在 GenGameObjects 中创建 长方形、正方形、球 及其色彩代表游戏中的对象。
使用 C# 集合类型 有效组织对象
整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!!! 。 整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的 通讯耦合 语句。 违背本条准则,不给分
请使用课件架构图编程,不接受非 MVC 结构程序
注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!

项目配置

游戏中提及的事物

首先将游戏中涉及到的游戏对象做成预制,包括左右的河岸,河水,三个牧师,三个魔鬼和一条小船。在游戏中,红色的代表魔鬼,白色的代表牧师。

unity编程实践 牧师与魔鬼

用表格列出规则表

动作 条件
开船 船上必须有一个人,船在左岸或者右岸
牧师在左岸上船 左岸必须有牧师,船上有空位,船在左岸
牧师在右岸上船 右岸必须有牧师,船上有空位,船在右岸
牧师在左岸下船 船上有牧师,船在左岸
牧师在右岸下船 船上有牧师,船在有岸
恶魔在左岸上船 左岸必须有恶魔,船上有空位,船在左岸
恶魔在右岸上船 右岸必须有恶魔,船上有空位,船在右岸
恶魔在左岸下船 船上有恶魔,船在左岸
恶魔在右岸下船 船上有恶魔,船在右岸

具体实现

在场景中设置一个空白对象用于放置代码,在Asserts目录下创建Resources文件夹,在在Resources中创建Prefabs文件夹,将做好的对象拖入Prefabs中成为预制

然后在Asserts目录下分别创建Model,Control,View文件夹,分别用来MVC架构中的三种代码。

Control文件夹中存放ControlGameObjects.cs,用来控制Model中船和人物的运动,检测玩家是否通关或者通关失败,然后控制View显示。

Model文件夹中存放EventClick.cs,EventClickPeoPle.cs,EventClick.cs用来控制船的行为以及一系列与船相关的状态参数;EventClickPeoPle.cs用来控制人物的行为以及一系列与牧师和魔鬼等人物有关的状态参数。

View文件夹中存放View.cs,用来控制界面的通知信息,如时间,RESTART按钮,以及标题。是否通关成功等信息。

代码细节

  • 1.ControlGameObjects.cs:

该模块充当director的角色,用来控制Model中船和人物的运动,检测玩家是否通关或者通关失败,然后控制View显示。

其中update()函数分情况讨论,如果游戏结束,则不再处理点击事件和检查。当点击按钮后,gameEndOrNot为Ture。重置所有状态信息,重开游戏;而当游戏还在进行中时,需要对人物和船的点击事件进行处理。

void Update()
{
    // 如果检测
    if (haveEndedGame && !river.GetComponent<View>().gameEndOrNot)
    {
        haveEndedGame = false;
        BeginGame();
        ReStartGame();
    }
    if (!haveEndedGame)
    {
        allPeopleClick();  //处理所有人物点击函数
        boatClick();       //处理船的点击函数
        Checked();
    }
    //检查游戏成功或者失败情况

}

游戏进行中对点击事件的处理:当预制被点击后,预制的脚本先通知Control,Control处理信息,通过检测预制的变量click来检测用户是否产生了点击行为,来确定移动是否符合条件,再告诉预制能不能进行移动。

首先是船移动点击响应函数boatClick():

public void boatClick()
{
    // 当所有人物运动都是静止的时候才可以开船;
    if ((boat.GetComponent<EventClick>().click && CountPeoPleOnBoat <= 0)) boat.GetComponent<EventClick>().click = false;
    if (boat.GetComponent<EventClick>().click && CountPeoPleOnBoat > 0 && allPeopleStanding())
    {

        if (priestOne.GetComponent<EventClickPeoPle>().onBoat)
        {
            priestOne.GetComponent<EventClickPeoPle>().AcrossRiver();
            CountPriestsNumOnEachCoast();
        }

        if (demonOne.GetComponent<EventClickPeoPle>().onBoat)
        {
            demonOne.GetComponent<EventClickPeoPle>().AcrossRiver();
            CountDemonsNumOnEachCoast();
        }

        if (priestTwo.GetComponent<EventClickPeoPle>().onBoat)
        {
            priestTwo.GetComponent<EventClickPeoPle>().AcrossRiver();
            CountPriestsNumOnEachCoast();
        }
        if (demonTwo.GetComponent<EventClickPeoPle>().onBoat)
        {
            demonTwo.GetComponent<EventClickPeoPle>().AcrossRiver();
            CountDemonsNumOnEachCoast();
        }

        if (priestThree.GetComponent<EventClickPeoPle>().onBoat)
        {
            priestThree.GetComponent<EventClickPeoPle>().AcrossRiver();
            CountPriestsNumOnEachCoast();
        }

        if (demonThree.GetComponent<EventClickPeoPle>().onBoat)
        {
            demonThree.GetComponent<EventClickPeoPle>().AcrossRiver();
            CountDemonsNumOnEachCoast();
        }

        boat.GetComponent<EventClick>().MoveAcrossRiver();
    }

}

然后是人物点击响应函数PeoPleClick(),分为移动到船或者移动到岸上

public void PeopleClick(ref GameObject gobj)
{
    if (gobj.GetComponent<EventClickPeoPle>().LeftOrRight != boat.GetComponent<EventClick>().leftSide) return; // 当船和移动的人物不在同一岸时不能移动
    gobj.GetComponent<EventClickPeoPle>().click = false;

    if (!gobj.GetComponent<EventClickPeoPle>().onBoat)  // 当牧师不在船上的时候 
    {
        if (CountPeoPleOnBoat >= 2) return;
        CountPeoPleOnBoat += 1;
        if (!peopleOnBoatLeft)
        {
            gobj.GetComponent<EventClickPeoPle>().MovePeoPle(1);
            peopleOnBoatLeft = true;
        }
        else
        {
            gobj.GetComponent<EventClickPeoPle>().MovePeoPle(2);
            peopleOnBoatRight = true;
        }


    }
    else
    {
        int onBoatLeftOrRight = gobj.GetComponent<EventClickPeoPle>().onBoatLeftOrRight;
        if (onBoatLeftOrRight == 1)
        {
            peopleOnBoatLeft = false;
        }
        else
        {
            peopleOnBoatRight = false;
        }
        gobj.GetComponent<EventClickPeoPle>().MovePeoPle(onBoatLeftOrRight);
        CountPeoPleOnBoat -= 1;

    }
}
  • 2.控制人物移动的部件 EventClickPeoPle.cs:

船岸移动函数MovePeoPle(),用来控制人物从岸上移动到船上,或者到从岸上到船上。

public void MovePeoPle(int t_onBoatPosition) // On...: 1代表在船的左边,2代表在船的右边;  
{
    
    // 选择路径
    if (!onBoat && !LeftOrRight)
    {
        rightToBoat = true;
    }
    else if (onBoat && !LeftOrRight)
    {
       
        boatToRight = true;
    }
    else if (!onBoat && LeftOrRight)
    {
        leftToBoat = true;
    }
    else if (onBoat && LeftOrRight)
    {
        boatToLeft = true;
    }
    bool right = false;
    if (rightToBoat || boatToRight) right = true;

    if (right)
    {
        if (t_onBoatPosition == 1)
        {
            onBoatPosition = new Vector3((float)1, (float)0.8, (float)-1.1);
            onBoatLeftOrRight = 1;

        }
        else if (t_onBoatPosition == 2)
        {
            onBoatPosition = new Vector3((float)1.5, (float)0.8, (float)-1.1);
            onBoatLeftOrRight = 2;
        }
    }else
    {
        if (t_onBoatPosition == 1)
        {
            onBoatPosition = new Vector3((float)-1.5, (float)0.8, (float)-1.1);
            onBoatLeftOrRight = 1;

        }
        else if (t_onBoatPosition == 2)
        {
            onBoatPosition = new Vector3((float)-1, (float)0.8, (float)-1.1);
            onBoatLeftOrRight = 2;
        }
    }
    
    pause = false;
}

人物随船移动函数AcrossRiver()

public void AcrossRiver()
{
    if (!onBoat || !pause) return;
    
    if (!LeftOrRight)
    {
        LeftOrRight = true;
        rightAcrossRiver = true;
        onBoatPosition = new Vector3((float)-2.5 + onBoatPosition.x, (float)0.8, (float)-1.1);
    }
    else
    {

        LeftOrRight = false;
        leftAcrossRiver = true;   //leftAcroossRiver 从左到右渡河
        
        onBoatPosition = new Vector3((float)2.5 + onBoatPosition.x, (float)0.8, (float)-1.1);
    }
    pause = false;
    

}

Update()函数中,rightToBoat的if语句部分为从右岸去船上的过程,其他情况以此类推。

void Update()
{
    if (pause) return;
    if (rightToBoat)
    {
        if (!MoveToBoat(ref rightFistStep, rightFirstDestination)) return;
        if (!MoveToBoat(ref rightMidStep, rightMidDestination)) return;
        if (!MoveToBoat(ref rightFinalStep, rightFinalDestination)) return;
        if (!MoveToBoat(ref finishOnBoatPosition, onBoatPosition)) return;

        finishOnBoatPosition = false;
        onBoat = true;
        pause = true;
        rightToBoat = false;

        rightZeroStep = false;
        rightFistStep = false;
        rightMidStep = false;
        rightFinalStep = false;
    }
    else if (boatToRight)
    {
        if (!MoveToBoat(ref rightFinalStep, rightFinalDestination)) return;
        if (!MoveToBoat(ref rightMidStep, rightMidDestination)) return;
        if (!MoveToBoat(ref rightFistStep, rightFirstDestination)) return;
        if (!MoveToBoat(ref rightZeroStep, rightStartPosition)) return;

        onBoat = false;
        pause = true;
        boatToRight = false;

        rightZeroStep = false;
        rightFistStep = false;
        rightMidStep = false;
        rightFinalStep = false;
    }

    else if (leftToBoat)
    {

        if (!MoveToBoat(ref leftFistStep, leftFirstDestination)) return;
        if (!MoveToBoat(ref leftMidStep, leftMidDestination)) return;
        if (!MoveToBoat(ref leftFinalStep, leftFinalDestination)) return;
        if (!MoveToBoat(ref finishOnBoatPosition, onBoatPosition)) return;
        finishOnBoatPosition = false;
        onBoat = true;
        pause = true;
        leftToBoat = false;
        leftZeroStep = false;
        leftFistStep = false;
        leftMidStep = false;
        leftFinalStep = false;
    }
    else if (boatToLeft)
    {
        if (!MoveToBoat(ref leftFinalStep, leftFinalDestination)) return;
        if (!MoveToBoat(ref leftMidStep, leftMidDestination)) return;
        if (!MoveToBoat(ref leftFistStep, leftFirstDestination)) return;
        if (!MoveToBoat(ref leftZeroStep, leftStartPosition)) return;
        onBoat = false;
        pause = true;
        boatToLeft = false;

        leftZeroStep = false;
        leftFistStep = false;
        leftMidStep = false;
        leftFinalStep = false;
    }
    else if (leftAcrossRiver) // 从左到右渡河
    {
        
        if (!MoveAcrossRiver(ref finishAcrossed, onBoatPosition)) return;

        pause = true;
        finishAcrossed = false;
        leftAcrossRiver = false;
    } 
    else if (rightAcrossRiver)
    {
        if (!MoveAcrossRiver(ref finishAcrossed, onBoatPosition)) return;
        pause = true;
        finishAcrossed = false;
        rightAcrossRiver = false;
    }
}

MoveToBoat()函数中,如果step为True,说明人物已经经过了目标位置。就直接返回,不用执行该函数。如果到了指定位置,就将Step改为True,表示目标已经到了。

bool MoveToBoat(ref bool step, Vector3 destination)
{
    if (step) return step;
    person.position = Vector3.MoveTowards(person.localPosition,destination, (float)5 * Time.deltaTime);
    if (person.position == destination)
    {
            step = true;
    }
    return step;
}
  • 3.控制船移动的部件是EvenClick.cs

船的移动比较简单,与人物的移动类似

void Update()
{
    if (!pause)
    {
        if (leftSide) {
            boat.position = Vector3.MoveTowards(boat.localPosition, new Vector3((float)1.3, (float)0.4, (float)-1), (float)2 * Time.deltaTime);
            if (boat.position == new Vector3((float)1.3, (float)0.4, (float)-1)) {
                pause = true;
                leftSide = false;
            }
            
        } else
        {
            boat.position = Vector3.MoveTowards(boat.localPosition, new Vector3((float)-1.3, (float)0.4, (float)-1), (float)2 * Time.deltaTime);
            if (boat.position == new Vector3((float)-1.3, (float)0.4, (float)-1)) {
                pause = true;
                leftSide = true;
            }
            
        }
    }
    
}

public void OnPointerClick(PointerEventData eventData)
{
    if (!pause) return;
    click = true;
}

public void MoveAcrossRiver()
{
    click = false;
    pause = false;
}

public void begin()
{
    boat.position = new Vector3((float)1.3, (float)0.4, (float)-1);
    pause = true;
    leftSide = false;
}
  • 4.View.cs主要用来控制界面的通知信息

OnGUI函数用于画出必要的通知信息

void OnGUI()
{

    //小字体初始化
    GUIStyle style = new GUIStyle();
    style.normal.textColor = Color.white;
    style.fontSize = 20;

    //大字体初始化
    GUIStyle bigStyle = new GUIStyle();
    bigStyle.normal.textColor = Color.white;
    bigStyle.fontSize = 30;

    GUI.Label(new Rect(150, 0, 50, 200), "Priests and Devils", bigStyle);
    GUI.Label(new Rect(0, 30, 100, 50), "Time: " + ShowTime, style);

    bigStyle.normal.textColor = Color.red;
    bigStyle.fontSize = 50;
    // "*YOU   LOSE*" "*YOU    WIN*"
    

    // 游戏结束
    if (gameEndOrNot)
    {
        if (GUI.Button(new Rect(240, 110, 100, 50), "RESTART"))
        {
            begin();
        }
        GUI.Label(new Rect(120, 50, 50, 200), ShowMessage, bigStyle);
    }
}

具体源代码见:https://gitee.com/zipzhou/priest-and-devil/tree/master

视频演示链接:https://www.bilibili.com/video/BV1Fb4y1Y7B7?spm_id_from=333.999.0.0

上一篇:[Linux]使用 ftrace 调试 Linux 内核 3


下一篇:C#中Ref和Out