2020-10-19

3D游戏第3次作业(牧师与魔鬼)

1.游戏对象运动的本质是什么?

对象位置属性,方向属性等的变化。

2.请用三种方法以上方法,实现物体的抛物线运动。(如,修改Transform属性,使用向量Vector3的方法)

用导数的方法,不断用当前position加上一个位移向量:
例如:(y=-x^2+3x)

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

public class NewBehaviourScript : MonoBehaviour
{
    public float speed;
 
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        float dx=Time.deltaTime*speed;
        Vector3 dir=new Vector3(dx,(-2*this.transform.position.x+3)*dx,0);
        this.transform.position+=dir;

    }
}

用lerp法

void Update()
    {
        float dx=Time.deltaTime*speed;
        Vector3 dir=new Vector3(dx,(-2*this.transform.position.x+3)*dx,0);
        this.transform.position=Vector3.Lerp(this.transform.position,this.transform.position+dir,1);

    }

用transl函数法:

 void Update()
    {
        float dx=Time.deltaTime*speed;
        Vector3 dir=new Vector3(dx,(-2*this.transform.position.x+3)*dx,0);
        this.transform.Translate(dir)}

结果抛物线一样。

3.编程实践《牧师与魔鬼》

游戏规则:

1.你要运用智慧帮助3个牧师(圆球)和3个魔鬼(方块)渡河。
2.船最多可以载2名游戏角色。
3.船上有游戏角色时,你才可以点击这个船,让船移动到对岸。
4.当有一侧岸的魔鬼数多余牧师数时(包括船上的魔鬼和牧师),魔鬼就会失去控制,吃掉牧师(如果5.这一侧没有牧师则不会失败),游戏失败。
6.当所有游戏角色都上到对岸时,游戏胜利。

游戏截图:

开始游戏
在这里插入图片描述

游戏失败
在这里插入图片描述

游戏胜利
在这里插入图片描述

游戏架构
使用了MVC架构。
在这里插入图片描述

场景中的所有GameObject就是Model,它们受到Controller的控制,比如说牧师和魔鬼受到MyCharacterController类的控制,船受到BoatController类的控制,河岸受到CoastController类的控制。
View就是UserGUI和ClickGUI,它们展示游戏结果,并提供用户交互的渠道(点击物体和按钮)。
Controller:除了刚才说的MyCharacterController、BoatController、CoastController以外,还有更高一层的Controller:FirstController(场景控制器),FirstController控制着这个场景中的所有对象,包括其加载、通信、用户输入。
最高层的Controller是Director类,一个游戏中只能有一个实例,它控制着场景的创建、切换、销毁、游戏暂停、游戏退出等等最高层次的功能。
Director
Director的定义

public class Director : System.Object {
    private static Director _instance;
    public SceneController currentSceneController { get; set; }

    public static Director getInstance() {
        if (_instance == null) {
            _instance = new Director ();
        }
        return _instance;
    }
}

Director是最高层的控制器,运行游戏时始终只有一个实例,它掌控着场景的加载、切换等,也可以控制游戏暂停、结束等等。

虽然Director控制着场景,但是它并不控制场景中的具体对象,控制场景对象的任务交给了SceneController(场景控制器),我们等一下会谈到。

Director类使用了单例模式。第一次调用Director.getInstance()时,会创建一个新的Director对象,保存在_instance,此后每次调用getInstance,都回返回_instance。也就是说Director最多只有一个实例。这样,我们在任何Script中的任何地方通过Director.getInstance()都能得到同一个Director对象,也就可以获得同一个currentSceneController,这样我们就可以轻易实现类与类之间的通信,比如说我在其他控制器中就可以使用Director.getInstance().somethingHappen()来告诉导演某一件事情发生了,导演就可以在somethingHappen()方法中做出对应的反应。

SceneController接口
SceneController接口定义:

public interface SceneController {
    void loadResources ();
}

interface(接口)不能直接用来创建对象!必须先有一个类实现(继承)它,在我的这个游戏中就是FirstController类。
SceneController 是用来干什么的呢?它是导演控制场景控制器的渠道。在上面的Director 类中,currentSceneController (FirstController类)就是SceneController的实现,所以Director可以调用SceneController接口中的方法,来实现对场景的生杀予夺。在这个游戏中SceneController的定义非常简单,因为这个游戏做得并不完整。我们刚才说过导演可以加载、切换、销毁场景、暂停游戏,所以SceneController 还可以规定void switchScene()、void destroyScene()、void pause()这些方法,供给导演来调用。

Moveable
Moveable是一个可以挂载在GameObject上的类:

public class Moveable: MonoBehaviour {
    
    readonly float move_speed = 20;

    // change frequently
    int moving_status;  // 0->not moving, 1->moving to middle, 2->moving to dest
    Vector3 dest;
    Vector3 middle;

    void Update() {
        if (moving_status == 1) {
            transform.position = Vector3.MoveTowards (transform.position, middle, move_speed * Time.deltaTime);
            if (transform.position == middle) {
                moving_status = 2;
            }
        } else if (moving_status == 2) {
            transform.position = Vector3.MoveTowards (transform.position, dest, move_speed * Time.deltaTime);
            if (transform.position == dest) {
                moving_status = 0;
            }
        }
    }
    public void setDestination(Vector3 _dest) {
        dest = _dest;
        middle = _dest;
        if (_dest.y == transform.position.y) {  // boat moving
            moving_status = 2;
        }
        else if (_dest.y < transform.position.y) {  // character from coast to boat
            middle.y = transform.position.y;
        } else {                                // character from boat to coast
            middle.x = transform.position.x;
        }
        moving_status = 1;
    }

    public void reset() {
        moving_status = 0;
    }
}

GameObject挂载上Moveable以后,Controller就可以通过setDestination()方法轻松地让GameObject移动起来。

在这里我没有让物体直接移动到目的地dest,因为那样可能会直接穿过河岸物体。我用middle来保存一个中间位置,让物体先移动到middle,再移动到dest,这就实现了一个折线的移动,不会穿越河岸。moving_status记录着目前该物体处于哪种移动状态。

MyCharacterController
MyCharacterController封装了一个GameObject,表示游戏角色(牧师或恶魔)。

public class MyCharacterController {
    readonly GameObject character;
    readonly Moveable moveableScript;
    readonly ClickGUI clickGUI;
    readonly int characterType; // 0->priest, 1->devil

    // change frequently
    bool _isOnBoat;
    CoastController coastController;


    public MyCharacterController(string which_character) {
        
        if (which_character == "priest") {
            character = Object.Instantiate (Resources.Load ("Perfabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
            characterType = 0;
        } else {
            character = Object.Instantiate (Resources.Load ("Perfabs/Devil", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
            characterType = 1;
        }
        moveableScript = character.AddComponent (typeof(Moveable)) as Moveable;

        clickGUI = character.AddComponent (typeof(ClickGUI)) as ClickGUI;
        clickGUI.setController (this);
    }

    public void setName(string name) {
        character.name = name;
    }

    public void setPosition(Vector3 pos) {
        character.transform.position = pos;
    }

    public void moveToPosition(Vector3 destination) {
        moveableScript.setDestination(destination);
    }

    public int getType() {  // 0->priest, 1->devil
        return characterType;
    }

    public string getName() {
        return character.name;
    }

    public void getOnBoat(BoatController boatCtrl) {
        coastController = null;
        character.transform.parent = boatCtrl.getGameobj().transform;
        _isOnBoat = true;
    }

    public void getOnCoast(CoastController coastCtrl) {
        coastController = coastCtrl;
        character.transform.parent = null;
        _isOnBoat = false;
    }

    public bool isOnBoat() {
        return _isOnBoat;
    }

    public CoastController getCoastController() {
        return coastController;
    }

    public void reset() {
        moveableScript.reset ();
        coastController = (Director.getInstance ().currentSceneController as FirstController).fromCoast;
        getOnCoast (coastController);
        setPosition (coastController.getEmptyPosition ());
        coastController.getOnCoast (this);
    }
}

UserAction
这个接口实际上使用了门面模式。
FirstController必须要实现这个接口才能对用户的输入做出反应。

public interface UserAction {
    void moveBoat();
    void characterIsClicked(MyCharacterController characterCtrl);
    void restart();
}

在这个游戏中,对用户输入做出反应,有这三个方法就够了。
UserAction是如何得到用户的输入的呢?原来,在ClickGUI和UserGUI这两个类中,都保存了一个UserAction的引用。当ClickGUI监测到用户点击GameObject的时候,就会调用这个引用的characterIsClicked方法,这样FirstController就知道哪一个游戏角色被点击了。UserGUI同理,只不过它监测的是“用户点击Restart按钮”的事件。

ClickGUI
ClickGUI类是用来监测用户点击,并调用SceneController进行响应的

public class ClickGUI : MonoBehaviour {
    UserAction action;
    MyCharacterController characterController;

    public void setController(MyCharacterController characterCtrl) {
        characterController = characterCtrl;
    }

    void Start() {
        action = Director.getInstance ().currentSceneController as UserAction;
    }

    void OnMouseDown() {
        if (gameObject.name == "boat") {
            action.moveBoat ();
        } else {
            action.characterIsClicked (characterController);
        }
    }
}

我们可以看到UserAction action实际上是FirstController的对象,它实现了UserAction接口。ClickGUI与FirstController打交道,就是通过UserAction接口的API。ClickGUI不知道这些API是怎么被实现的,但它知道FirstController类一定有这些方法。

项目代码

项目源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值