2018-04-02 17:53:27 qq874455953 阅读数 8277

用Unity3D模拟太阳系仿真

模拟要求

写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。

操作步骤

1.创建如下结构 sun 里包括8大行星, 并且设置好距离和大小

建立结构

image

建议用2D显示来直观设置距离

在这里插入图片描述

2.在网上找到相应贴图 添加到assets

贴图网址
网址失效 素材更新链接如下
链接: https://pan.baidu.com/s/1w0xSMt_0HgBLbVhco9b6GA 提取码: ead5

在这里插入图片描述
而且把对应行星的贴图图片拖到对应的球体上(给白色小球上色)得到下面结果

在这里插入图片描述

3.创建c#脚本 使每个行星绕太阳转

创建plantMove.cs文件 代码如下

把此脚本拖到sun 物体即可,代码就是在sun 里面找到各个行星,并且设置运动

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

public class plantMove : MonoBehaviour
{

    // Use this for initialization  
    void Start()
    {
   
    }

    // Update is called once per frame  
    void Update()
    {

        GameObject.Find("Sun").transform.Rotate(Vector3.up * Time.deltaTime * 5 );

        GameObject.Find("Mercury").transform.RotateAround(Vector3.zero, new Vector3(0.1f, 1, 0), 60 * Time.deltaTime);
        //设置公转的方向和速度  方向轴为(0, 1, 0) 速度为 60
        GameObject.Find("Mercury").transform.Rotate(Vector3.up * Time.deltaTime * 10000 / 58);
        //设置自转 自转速度为10000/58   58是水星的自传周期  倒数就是时间  下同

        GameObject.Find("Venus").transform.RotateAround(Vector3.zero, new Vector3(0, 1, -0.1f), 55 * Time.deltaTime);
        GameObject.Find("Venus").transform.Rotate(Vector3.up * Time.deltaTime * 10000 / 243);

        GameObject.Find("Earth").transform.RotateAround(Vector3.zero, new Vector3(0, 1, 0), 50 * Time.deltaTime);
        GameObject.Find("Earth").transform.Rotate(Vector3.up * Time.deltaTime * 10000);

         GameObject.Find("Moon").transform.RotateAround(Vector3.zero, new Vector3(0, 1, 0), 5 * Time.deltaTime);
        GameObject.Find("Moon").transform.Rotate(Vector3.up * Time.deltaTime * 10000/27);

        GameObject.Find("Mars").transform.RotateAround(Vector3.zero, new Vector3(0.2f, 1, 0), 45 * Time.deltaTime);
        GameObject.Find("Mars").transform.Rotate(Vector3.up * Time.deltaTime * 10000);

        GameObject.Find("Jupiter").transform.RotateAround(Vector3.zero, new Vector3(-0.1f, 2, 0), 35 * Time.deltaTime);
        GameObject.Find("Jupiter").transform.Rotate(Vector3.up * Time.deltaTime * 10000 / 0.3f);

        GameObject.Find("Saturn").transform.RotateAround(Vector3.zero, new Vector3(0, 1, 0.2f), 20 * Time.deltaTime);
        GameObject.Find("Saturn").transform.Rotate(Vector3.up * Time.deltaTime * 10000 / 0.4f);

        GameObject.Find("Uranus").transform.RotateAround(Vector3.zero, new Vector3(0, 2, 0.1f), 15 * Time.deltaTime);
        GameObject.Find("Uranus").transform.Rotate(Vector3.up * Time.deltaTime * 10000 / 0.6f);

        GameObject.Find("Neptune").transform.RotateAround(Vector3.zero, new Vector3(-0.1f, 1, -0.1f), 10 * Time.deltaTime);
        GameObject.Find("Neptune").transform.Rotate(Vector3.up * Time.deltaTime * 10000 / 0.7f);

    }
}

代码解释:

我们通过GameObject.Find(“value”)来找到各个球体 然后通过调用RotateAround()设置公转,通过调用Rotate()方法设置自转

4. 进一步思考

运行下发现月球绕地球旋转不对劲, 其原因是地球的自转影响了月球的公转, 月球是相对一个自转的地球来的 月球公转叠加了地球自转。

所以怎么去处理这个问题呢? 怎么让月球公转和地球自转不相关呢?

这里有一个解决方案

在原来的Sun里再加入一个对象名为EarthClone,而且大小和位置都和地球相同, 然后我对EarthClone设置和Earth一样的公转 但是不设置自转 把Moon放到EarthClone里让Moon相对与EarthClone公转 这样就能解决问题了

文件结构:

在这里插入图片描述
加入和更改下列代码

 void Update()
    {

        GameObject.Find("Sun").transform.Rotate(Vector3.up * Time.deltaTime * 5 );

        GameObject.Find("Mercury").transform.RotateAround(Vector3.zero, new Vector3(0.1f, 1, 0), 60 * Time.deltaTime);
        GameObject.Find("Mercury").transform.Rotate(Vector3.up * Time.deltaTime * 1 / 58);

        GameObject.Find("Venus").transform.RotateAround(Vector3.zero, new Vector3(0, 1, -0.1f), 55 * Time.deltaTime);
        GameObject.Find("Venus").transform.Rotate(Vector3.up * Time.deltaTime * 10 / 243);

        GameObject.Find("Earth").transform.RotateAround(Vector3.zero, new Vector3(0, 1, 0), 50 * Time.deltaTime);
        GameObject.Find("Earth").transform.Rotate(Vector3.up * Time.deltaTime * 10);
        //只设置公转 不设置自传
        GameObject.Find("EarthClone").transform.RotateAround(Vector3.zero, new Vector3(0, 1, 0), 50 * Time.deltaTime);
        //令Moon相对与不自转的EarthClone公转
        GameObject.Find("Moon").transform.RotateAround(GameObject.Find("EarthClone").transform.position, new Vector3(0, 1, 0), 250 * Time.deltaTime);
        GameObject.Find("Moon").transform.Rotate(Vector3.up * Time.deltaTime * 10/27);

        GameObject.Find("Mars").transform.RotateAround(Vector3.zero, new Vector3(0.2f, 1, 0), 45 * Time.deltaTime);
        GameObject.Find("Mars").transform.Rotate(Vector3.up * Time.deltaTime * 10);

        GameObject.Find("Jupiter").transform.RotateAround(Vector3.zero, new Vector3(-0.1f, 2, 0), 35 * Time.deltaTime);
        GameObject.Find("Jupiter").transform.Rotate(Vector3.up * Time.deltaTime * 10 / 0.3f);

        GameObject.Find("Saturn").transform.RotateAround(Vector3.zero, new Vector3(0, 1, 0.2f), 20 * Time.deltaTime);
        GameObject.Find("Saturn").transform.Rotate(Vector3.up * Time.deltaTime * 10 / 0.4f);

        GameObject.Find("Uranus").transform.RotateAround(Vector3.zero, new Vector3(0, 2, 0.1f), 15 * Time.deltaTime);
        GameObject.Find("Uranus").transform.Rotate(Vector3.up * Time.deltaTime * 10 / 0.6f);

        GameObject.Find("Neptune").transform.RotateAround(Vector3.zero, new Vector3(-0.1f, 1, -0.1f), 10 * Time.deltaTime);
        GameObject.Find("Neptune").transform.Rotate(Vector3.up * Time.deltaTime * 10 / 0.7f);

    }

5.结果显示

把cs脚本拖到Sun对象里 运行查看结果
在这里插入图片描述

2018-04-04 08:44:53 ShenDW818 阅读数 2574

      太阳系大概是什么样子,大家都知道的,这里就不多说了。太阳系模拟,本来应该是按太阳系中太阳和行星的真实比例来实现的,后来,发现用真实比例,呈现出来的模拟结果很难看(太阳确实太大了,另外,大的行星和小的行星之间的比例相差也太大),所以,这次实验就没有按照真实的数据比例来进行模拟啦。
      先来看看成品图:
这里写图片描述
这里写图片描述
这里写图片描述

下面,就看一下怎么弄吧

首先,就先下载好太阳和各行星的贴图:
这里写图片描述
并且用这些贴图来弄好太阳和各行星的prefabs:
这里写图片描述

接下来,就是代码实现了。

首先,就是导演类(单例模式,一个游戏只有一个导演)和场景控制接口,这两个就没什么可说的啦。

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

public class SSDirector : System.Object {
    //singlton instance
    private static SSDirector _instance;

    public IScenesController currentScenceController { get; set; }

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

    public int getFPS()
    {
        return Application.targetFrameRate;
    }

    public void setFPS(int fps)
    {
        Application.targetFrameRate = fps;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IScenesController
{
    void LoadResources();
}

然后,就是实现整个场景的控制器了。这里就不多说什么,看代码和注释胜过很多语言表达。

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

public class Controller : MonoBehaviour, IScenesController {
    private Transform[] planets = new Transform[8];
    //行星名字,用于辅助后面资源导入
    private string[] planets_name = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" };
    //三个摄影机,用来处理三个不同的拍摄视角
    private GameObject ca_0;
    private GameObject ca_1;
    private GameObject ca_2;

    //行星的初始位置,其实也可以看作是公转半径比例啦
    Vector3[] position =
    {
        new Vector3(6, 0, 0),
        new Vector3(10, 0, 0),
        new Vector3(14, 0, 0),
        new Vector3(23, 0, 0),
        new Vector3(38, 0, 0),
        new Vector3(53, 0, 0),
        new Vector3(77, 0, 0),
        new Vector3(90, 0, 0)
    };
    //各行星的公转法平面倾角
    Vector3[] includedAngle = {
        new Vector3 (0, 1, Mathf.Tan(Mathf.PI/180*7)),
        new Vector3 (0, 1, Mathf.Tan(Mathf.PI/180*3.4f)),
        new Vector3 (0, 1, 0),
        new Vector3 (0, 1, Mathf.Tan(Mathf.PI/180*1.9f)),
        new Vector3 (0, 1, Mathf.Tan(Mathf.PI/180*1.3f)),
        new Vector3 (0, 1, Mathf.Tan(Mathf.PI/180*2.5f)),
        new Vector3 (0, 1, Mathf.Tan(Mathf.PI/180*0.8f)),
        new Vector3 (0, 1, Mathf.Tan(Mathf.PI/180*1.8f)),
    };
    //公转速率和自转速率。公转的胡乱编的,自转倒是比较真实
    float[] gongZhuan = { 40.0f, 16.0f, 10.0f, 8.0f, 6.0f, 8.0f, 5.0f, 6.0f };
    float[] ziZhuan = { 6.1f, -1.5f, 360f, 350f, 878f, 844.5f, -500.5f, 540.5f };

    Transform Sun;//太阳组件,作为所有行星的父亲
    //两个背景。至于为什么两个,就为了多个视角,然后,技术不够,只能用这种方法来实现背景
    Transform background1;
    Transform background2;
    //导演的设定
    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.setFPS(60);
        director.currentScenceController = this;
        director.currentScenceController.LoadResources();

    }
    //生成transform组件的辅助函数
    Transform createTransform(Vector3 position, string name)
    {
        Transform transform = Instantiate<Transform>(Resources.Load<Transform>("prefabs/" + name), position, Quaternion.identity);
        return transform;
    }
    //资源导入
    public void LoadResources()
    {   //导入太阳
        Sun = createTransform(Vector3.zero, "Sun");
        Sun.name = "Sun";
        //导入背景
        background1 = createTransform(new Vector3(0, -100, 500), "background");
        background1.name = "Background";
        background1.localScale += new Vector3(2000, 1000, 100);

        background2 = createTransform(new Vector3(0, -500, 0), "background");
        background2.name = "tBackground";
        background2.localScale += new Vector3(2000, 2, 2000);
        //导入各个行星
        for (int i = 0; i < 8; i++)
        {
            planets[i] = createTransform(position[i], planets_name[i]);
            planets[i].transform.parent = Sun.transform;
            planets[i].name = planets_name[i];
            //用来加尾巴的。不过,有一些属性我试过改了,呈现不出来,就干脆,原始这样子吧
            TrailRenderer trail = planets[i].gameObject.AddComponent<TrailRenderer>();
            trail.startWidth = 0.1f;
        }
    }

    // 设置初始化视角为远看
    void Start () {
        ca_0 = GameObject.Find("Camera");
        ca_0.SetActive(true);
        ca_1 = GameObject.Find("Camera1");
        ca_1.SetActive(false);
        ca_2 = GameObject.Find("Camera2");
        ca_2.SetActive(false);
    }

    // Update is called once per frame
    void Update () {
        //每个行星的自转和公转处理
        for(int i = 0; i < 8; i++)
        {
            planets[i].RotateAround(Sun.position, includedAngle[i], gongZhuan[i] * Time.deltaTime);
            planets[i].Rotate(Vector3.up * ziZhuan[i] * Time.deltaTime);
        }
        //获取键盘输入来得到观察的三个视角。C是近处,F是远处,R是俯视
        if(Input.GetKeyDown(KeyCode.C))
        {
            ca_0.SetActive(false);
            ca_1.SetActive(true);
            ca_2.SetActive(false);
        }
        if(Input.GetKeyDown(KeyCode.F))
        {
            ca_1.SetActive(false);
            ca_0.SetActive(true);
            ca_2.SetActive(false);
        }
        if(Input.GetKeyDown(KeyCode.R))
        {
            ca_2.SetActive(true);
            ca_1.SetActive(false);
            ca_0.SetActive(false);
        }
    }
    //为使用添加一点说明
    void OnGUI()
    {
        GUIStyle fStyle = new GUIStyle();
        fStyle.fontSize = 40;
        fStyle.normal.textColor = Color.red;//红色比较容易看清
        GUI.Label(new Rect(0, 0, 200, 50), "视角:C为近看,F为远看,R为俯视", fStyle);
    }
}

       代码实现完之后,也得对场景做一下处理,也很简单,就是把一个空对象和三个摄像头加上去,调整好它们的位置就好了。这里的话,为了观察方便,我三个摄像头都加了一个平行光,将光的强度调到了0.1。然后,在太阳的位置(就是Vector(0,0,0))上,增加一个点光,模拟太阳发出来的光线。
       最后,由于点光源不会把太阳照亮,感觉看起来太阳比其他行星还暗,为了解决这个问题,我去下载了一个Glow11的package,然后将这个package导入场景中。然后,在太阳prefab上,改一下Shader属性就可以了。
这里写图片描述
这里写图片描述
之后,太阳看起来就是发亮的样子了。至此,这个太阳系就模拟地差不多了。

2018-04-03 23:03:54 weixin_34023863 阅读数 103

Unity3D - 模拟太阳系

模拟太阳系动态图(如不能显示请点进去访问)

首先是制作太阳系中的每个行星,基本上都是先创建Sophere,然后改变起始位置,添加材质和贴图,这里就不赘述了。
模拟太阳系 - 1
给每个行星创建材质包:
行星材质

之后就是创建一个行星的移动脚本使得行星绕太阳公转起来,这里需要注意的就是随机选取或者自己设一个参照轴,使得每颗行星公转的法平面不同。

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

public class Move : MonoBehaviour {
    public Transform origin;
    public float speed;
    float ry, rx;
    // Use this for initialization  
    void Start()
    {
        speed = Random.Range(9, 12);
        rx = Random.Range(10, 60);
        ry = Random.Range(10, 60);
    }

    // Update is called once per frame  
    void Update()
    {
        this.transform.RotateAround(origin.position, new Vector3(0, rx, ry), speed * Time.deltaTime);
    }
}

将脚本挂载到所有行星上后所有行星就能动起来了。但是行星还不能自转,于是添加一个自转脚本挂载到所有星球上:

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

public class Rotation : MonoBehaviour {

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        this.transform.RotateAround(this.transform.position, Vector3.up, Random.Range(1, 3));    
    }
}

这时所有的行星的移动就已经搞定了,需要注意的就是月亮绕地球的旋转需要一个单独的脚本,设定以地球为旋转圆心:

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

public class Moon_Move : MonoBehaviour {
    public Transform origin;
    public float speed = 4;
    float ry, rx;
    // Use this for initialization  
    void Start()
    {
        rx = Random.Range(10, 60);//随机选取旋转轴向量
        ry = Random.Range(10, 60);
    }

    // Update is called once per frame  
    void Update()
    {
        this.transform.RotateAround(origin.position, new Vector3(0, rx, ry), speed * Time.deltaTime);
    }
}

最后发现太阳系太过孤单,太空怎么能少了星海作为背景?添加一个背景板,贴上星空的图片作为背景美化一下:
星空背景

2017-05-20 12:24:49 marvelgl 阅读数 1295

目标:

写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。
基本思路是在里面创建对象,架构成一个太阳系,sun作为父对象,其他行星作为子对象,并且相对sun的初始位置均不一样,那么角速度相同的情况下转速就不一样了,另外法平面是采取随机分配一个方向给任意一个行星,只要y和z的比值不一样,那么就不会在同一个轨道上。


实现过程:

如图建立对象,并放到合适位置,改好名字
这里写图片描述
把脚本挂到Sun上,并给Script中的Transform对象赋值(把对应对象拉进去就行)
这里写图片描述
另外新建Material挂在星球上可以让星球看上去更好看,以下是地球的,图片网上搜的:
这里写图片描述
这里写图片描述
效果图:
这里写图片描述
这里写图片描述

我给太阳加了光源,让他能有照明的效果(在我的另一篇博客有对光源更多的介绍)
这里写图片描述


实现代码:

sunset.cs

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

public class sunset : MonoBehaviour {
    public Transform sun;
    public Transform mercury;
    public Transform venus;
    public Transform earth;
    public Transform mars;
    public Transform jupiter;
    public Transform saturn;
    public Transform uranus;
    public Transform neptune;
    public Transform moon;
    // Use this for initialization
    Vector3 []a = new Vector3[9];
    float speed = 40;
    float y, z;
    void Start () {
        int i = 0;
        for (i = 0; i < 9; i++) {
            y = Random.Range(1, 360); // 随机设置角度
            z = Random.Range(1, 360); // 随机设置角度
            a[i] = new Vector3(0, y, z); // 以上是为了制造不同的运动法平面,修改y和z可以使得绕不同的轴转
        }
    }

    // Update is called once per frame
    void Update () { // 每个星球的旋转动作,用到了初始化的a[i]
        mercury.RotateAround(sun.position, a[0], speed*Time.deltaTime);
        mercury.Rotate(Vector3.up *speed *Time.deltaTime);
        venus.RotateAround(sun.position, a[1], speed*Time.deltaTime);
        venus.Rotate(Vector3.up *speed *Time.deltaTime);
        earth.RotateAround(sun.position, a[2], speed*Time.deltaTime);
        earth.Rotate(Vector3.up *speed *Time.deltaTime);
        mars.RotateAround(sun.position, a[3], speed*Time.deltaTime);
        mars.Rotate(Vector3.up *speed *Time.deltaTime);
        jupiter.RotateAround(sun.position, a[4], speed*Time.deltaTime);
        jupiter.Rotate(Vector3.up *speed *Time.deltaTime);
        saturn.RotateAround(sun.position, a[5], speed*Time.deltaTime);
        saturn.Rotate(Vector3.up *speed *Time.deltaTime);
        uranus.RotateAround(sun.position, a[6], speed*Time.deltaTime);
        uranus.Rotate(Vector3.up *speed *Time.deltaTime);
        neptune.RotateAround(sun.position, a[7], speed*Time.deltaTime);
        neptune.Rotate(Vector3.up *speed *Time.deltaTime);
        moon.RotateAround(earth.position, Vector3.right, 400 *Time.deltaTime);
    }
}
2018-04-03 20:16:23 dickdick111 阅读数 128

作业内容

本文主要针对unity的游戏对象的移动做出介绍,并通过几个简易的代码实现抛物线运动,运用物体的旋转完成简单的太阳系模拟构造。最后,根据MVC模式实现牧师与恶魔小游戏。


1、简答并用程序验证

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

  游戏对象运动的本质就是坐标的变换,包括世界坐标(绝对坐标)的变换和对象坐标(相对坐标)的变换。每个游戏对象必须有一个Transform组件,我们可以通过改变该组件的属性来让游戏对象运动起来。


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

  • 利用transform中的position属性方法直接改变位置
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript1 : MonoBehaviour {
    public int a = 2;
    private void Update()
    {
        this.transform.position += Vector3.right * Time.deltaTime * 1f;
        this.transform.position += Vector3.down * Time.deltaTime * Time.deltaTime * a;
    }
}
  • 利用Vector3的MoveTowards方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class moveup : MonoBehaviour {
    //The target marker
    Vector3 target1 = Vector3.right * 5;
    Vector3 target2 = Vector3.down * 5;
    float speed1 = 1;
    float speed2 = 4;
    // Update is called once per frame
    void Update(){
        float step1 = speed1 * Time.deltaTime;
        float step2 = speed2 * Time.deltaTime * Time.deltaTime;
        transform.position = Vector3.MoveTowards(transform.position, target1, step1);
        transform.position = Vector3.MoveTowards(transform.position, target2, step2);
    }
}
  • 使用Vector3.Lerp()方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript1 : MonoBehaviour {
    public float Speed = 0.5f;
    Vector3 Target1 = new Vector3(-6, -3, 8);
    //控制物体向Target移动
    void Update()
    {
        gameObject.transform.localPosition = Vector3.Lerp(transform.position, Target1, Speed * Time.deltaTime);
    }
}
  • 利用transform.Translate()方法
    function Translate (translation : Vector3, relativeTo : Space = Space.Self) : void
    物体以relativeTo为参照系,沿着translation运动|translation|的距离。如果relativeTo缺省将以Space.Self为默认值。

C.写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。

效果

//对于八大行星围绕太阳公转
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript1 : MonoBehaviour {
    public Transform sun;
    public float speed = 15;
    private float random_x, random_y;
    //由于题目需要行星要在不同的法平面旋转,故我们随机行星旋转的轴位置
    private void Start()
    {
        random_x = Random.Range(1, 10);
        random_y = Random.Range(10, 60);
    }
    void Update()
    {
        Vector3 axis = new Vector3(random_x, random_y, 0); //围绕哪一条轴旋转
        this.transform.RotateAround(sun.position, axis, speed * Time.deltaTime);
    }
}
//对于地球的自转
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateSelf : MonoBehaviour {
    // Update is called once per frame
    void Update () {
        this.transform.RotateAround(this.transform.position, Vector3.up, 2);
    }
}

2、编程实践

A. 阅读以下游戏脚本

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!

B. 程序需要满足的要求:

规则名称 条件
牧师或魔鬼上左岸 船已靠左岸,船上有牧师或魔鬼
牧师或魔鬼上右岸 船已靠右岸,船上有牧师或魔鬼
魔鬼上船 岸上有魔鬼,船未载满两人
牧师上船 岸上有牧师,船未载满两人
船只开动 船上有人
击杀规则 一边的牧师数量少于魔鬼的数量
游戏胜利规则 所有角色从左岸到达右岸,且全部存活
游戏失败规则 牧师被击杀
  • 请将游戏中对象做成预制

Perfabs

  • 在 GenGameObjects 中创建 长方形、正方形、球 及其色彩代表游戏中的对象。

  白正方形表示牧师,黑球代表魔鬼,棕色长方体代表船,绿色长方体左右岸,蓝色长方体代表河流。

  • 使用 C# 集合类型 有效组织对象
  • 整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!整个游戏不许出现 Find 游戏对象,SendMessage 这类突破程序结构的通讯耦合语句。
  • 请使用课件架构图编程,不接受非 MVC 结构程序
    mvc
  • 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!

游戏中所用到的主要概念:

1.首先我们要理解MVC模式

模型(Model)– GameObject 及其关系
控制(Controller)– MonoBehavior
视图(View)– Camera

2.创建 SSDirector 对象,其职责大致如下:

  • 获取当前游戏的场景
  • 控制场景运行、切换、入栈与出栈
  • 暂停、恢复、退出
  • 管理游戏全局状态
  • 设定游戏的配置
  • 设定游戏全局视图

3. SCENECONTROLLER(场记)

  • 管理本次场景所有的游戏对象
  • 协调游戏对象(预制件级别)之间的通信
  • 响应外部输入事件
  • 管理本场次的规则(裁判)
  • 杂务

4.接口(INTERFACE)

  • 一种数据类型,表示对象的对外行为类型
  • 每个场景都有自己的场记,导演需要与不同场景打交道
  • 导演只知道场记的加载资源、暂停、恢复等行为,但并不知道实现细节,如:暂停前要保存哪些状态等
  • 导演也不希望知道细节,而是规定场记们必须会做什么,怎么做自由发挥。这个规定就是接口

5. 接口与门面(FASÀDE)模式

  • 外部与一个子系统的通信必须通过一个统一的门面
    (Facade)对象进行。

解释代码

1.Director为整个游戏的导演,要使用单例模式。它掌控着场景的加载、切换等,也可以控制游戏暂停、结束等等。

 //Director 控制唯一一个实例
    public class Director : System.Object
    {
        private static Director D_instance;
        public SceneController sceneController { get; set; }

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

2.场景接口

//场景控制器,被FirstController类所继承来加载资源预设
    public interface SceneController
    {
        void load();
    }

3.门面模式 ,FirstController实现该接口来与用户交互。

    public interface UserAction
    {
        void moveBoat();
        void characterIsClicked(MyCharacterController c_controller);
        void restart();
        bool stop();
    }

4.生成角色的类

 //角色的类
    public class MyCharacterController
    {
        //只读数据,不希望通过Inspector中改变角色
        readonly GameObject character;
        readonly Moveable moveable;
        readonly ClickGUI clickGUI;
        readonly bool is_priest;
        //可改变
        bool is_onboat;
        CoastController coastController;
        //通过字符串构造角色函数
        public MyCharacterController(string c_str)
        {
            if(c_str == "priest")
            {
                is_priest = true;
                character = Object.Instantiate(Resources.Load("Perfabs/priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
            }
            else if(c_str == "devil")
            {
                is_priest = false;
                character = Object.Instantiate(Resources.Load("Perfabs/devil", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
            }
            moveable = character.AddComponent(typeof(Moveable)) as Moveable;
            clickGUI = character.AddComponent(typeof(ClickGUI)) as ClickGUI;
            clickGUI.setController(this);
        }
        //角色上船函数
        public void Onboat(BoatController boatController)
        {
            coastController = null; //离开岸边
            character.transform.parent = boatController.getBoat().transform;
            is_onboat = true;
        }
        //角色上岸函数
        public void Oncoast(CoastController temp)
        {
            coastController = temp;
            character.transform.parent = null;
            is_onboat = false;
        }
        //重置函数,恢复现场
        public void reset()
        {
            moveable.reset();
            coastController = (Director.getInstance().sceneController as FirstController).leftCoast;
            Oncoast(coastController);
            setPosition(coastController.getEmptyPosition());
            coastController.getOnCoast(this);
        }
        //各种get,set函数
        public void setName(string name)
        {
            character.name = name;
        }
        public string getName()
        {
            return character.name;
        }
        public void setPosition(Vector3 position)
        {
            character.transform.position = position;
        }
        public Vector3 getPosition()
        {
            return character.transform.position;
        }
        public void movePosition(Vector3 position)
        {
            moveable.setDestination(position);
        }
        public bool getType() //true -> priest; false -> devil
        {
            return is_priest;
        }
        public bool getis_onboat()
        {
            return is_onboat;
        }
        public CoastController getcoastController()
        {
            return coastController;
        }
    }

4.生成船的类

 //船的类
    public class BoatController
    {
        //只读数据,不希望通过Inspector中改变船的位置
        readonly GameObject boat;
        readonly Moveable moveable;
        readonly Vector3 right_pos = new Vector3(4, 1, 0);
        readonly Vector3 left_pos = new Vector3(-4, 1, 0);
        readonly Vector3[] start_pos;
        readonly Vector3[] end_pos;
        //判断船是否向左岸走
        bool is_left;
        //船上的角色最多只有两个
        MyCharacterController[] passenger = new MyCharacterController[2];
        //船的构造函数
        public BoatController()
        {
            is_left = true;
            end_pos =  new Vector3[] { new Vector3(3F, 2F, 0), new Vector3(4.5F, 2F, 0) };
            start_pos = new Vector3[] { new Vector3(-4.5F, 2F, 0), new Vector3(-3F, 2F, 0) };
            boat = Object.Instantiate(Resources.Load("Perfabs/boat", typeof(GameObject)), left_pos, Quaternion.identity, null) as GameObject;
            boat.name = "boat";
            moveable = boat.AddComponent(typeof(Moveable)) as Moveable;
            boat.AddComponent(typeof(ClickGUI));
        }
        //判断船是否为空
        public bool isEmpty()
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null)
                    return false;
            }
            return true;
        }
        //查找船上的空位
        public int getEmptyIndex()
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if (passenger[i] == null) return i;
            }
            return -1;
        }
        //查找船上空位的位置
        public Vector3 getEmptyPos()
        {
            int index = getEmptyIndex();
            if (is_left)
                return start_pos[index];
            else
                return end_pos[index];
        }
        //船的移动函数,通过调用moveable中的setDestination函数
        public void boat_move()
        {
            if (is_left)
            {
                is_left = false;
                moveable.setDestination(right_pos);
            }
            else
            {
                is_left = true;
                moveable.setDestination(left_pos);
            }
        }
        //上船函数
        public void GetOnBoat(MyCharacterController charactercontroller)
        {
            int index = getEmptyIndex();
            if(index != -1)
                passenger[index] = charactercontroller;
        }
        //上岸函数
        public MyCharacterController GetOffBoat(string name)
        {
            for(int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null && passenger[i].getName() == name)
                {
                    MyCharacterController mycharacter = passenger[i];
                    passenger[i] = null;
                    return mycharacter;
                }
            }
            return null;
        }
        //重置函数
        public void reset()
        {
            moveable.reset();
            if(is_left == false)
            {
                boat_move();
            }
            passenger = new MyCharacterController[2];
        }
        //各种get和set函数
        public bool get_is_left()
        {
            return is_left;
        }
        public GameObject getBoat()
        {
            return boat;
        }
        public int[] getCharacterNum()
        {
            int[] count = { 0, 0 };
            for(int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null)
                {
                    if(passenger[i].getType() == true)
                    {
                        count[0]++;
                    }
                    else
                    {
                        count[1]++;
                    }
                }
            }
            return count;
        }
    }

5.生成岸边的类

//岸的类
    public class CoastController
    {
        //只读数据,不希望通过Inspector中改变左右岸的位置
        readonly GameObject coast;
        readonly Vector3 right_pos = new Vector3(10, 1, 0);
        readonly Vector3 left_pos = new Vector3(-10, 1, 0);
        //角色在岸上的位置
        readonly Vector3[] positions;
        //岸是否在右边
        readonly bool is_right;

        MyCharacterController[] passenger;

        public CoastController(string pos)
        {
            positions = new Vector3[] {new Vector3(6.5F,2.6F,0), new Vector3(7.7F,2.6F,0), new Vector3(8.9F,2.6F,0),
                new Vector3(10.1F,2.6F,0), new Vector3(11.3F,2.6F,0), new Vector3(12.5F,2.6F,0)};
            passenger = new MyCharacterController[6];
            if (pos == "right")
            {
                coast = Object.Instantiate(Resources.Load("Perfabs/coast", typeof(GameObject)), right_pos, Quaternion.identity, null) as GameObject;
                coast.name = "right";
                is_right = true;
            }
            else if (pos == "left")
            {
                coast = Object.Instantiate(Resources.Load("Perfabs/coast", typeof(GameObject)), left_pos, Quaternion.identity, null) as GameObject;
                coast.name = "left";
                is_right = false;
            }
        }
        //获得空位函数
        public int getEmptyIndex()
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if (passenger[i] == null)
                {
                    return i;
                }
            }
            return -1;
        }
        //获得空位位置函数
        public Vector3 getEmptyPosition()
        {
            Vector3 pos = positions[getEmptyIndex()];
            if (is_right == false)
                pos.x *= -1;
            return pos;
        }
        //上岸函数
        public void getOnCoast (MyCharacterController mycharacter)
        {
            passenger[getEmptyIndex()] = mycharacter;
        }
        //离岸函数
        public MyCharacterController getOffCoast(string name)
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null && passenger[i].getName() == name)
                {
                    MyCharacterController mycharacter = passenger[i];
                    passenger[i] = null;
                    return mycharacter;
                }
            }
            return null;
        }
        //重置函数
        public void reset()
        {
            passenger = new MyCharacterController[6];
        }
        //各种get和set函数
        public bool get_is_right()
        {
            return is_right;
        }
        public int[] getCharacterNum()
        {
            int[] count = { 0, 0 };
            for(int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null)
                {
                    if(passenger[i].getType() == true)
                    {
                        count[0]++;
                    }
                    else
                    {
                        count[1]++;
                    }
                }
            }
            return count;
        }
    }

6.移动函数的类

public class Moveable : MonoBehaviour
    {
        public float speed = 20;
        int status;  // 0->not moving, 1->moving to boat, 2->moving to dest
        Vector3 dest;
        Vector3 boat;
        void Update()
        {
            if(status == 1)
            {
                transform.position = Vector3.MoveTowards(transform.position, boat, speed * Time.deltaTime);
                if(transform.position == boat)
                    status = 2;
            }
            else if(status == 2)
            {
                transform.position = Vector3.MoveTowards(transform.position, dest, speed * Time.deltaTime);
                if (transform.position == dest)
                    status = 0;
            }
        }
        //设置目的地
        public void setDestination(Vector3 pos)
        {
            dest = boat = pos;
            if (pos.y < transform.position.y)      
            {       
                boat.y = transform.position.y;
            }
            else if(pos.y > transform.position.y)
            {                               
                boat.x = transform.position.x;
            }
            status = 1;
        }
        public void reset()
        {
            status = 0;
        }
    }

7.ClickGUI函数,用户通过鼠标点击来交互,这就是MVC中的View部分

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Mygame;

public class ClickGUI : MonoBehaviour {
    UserAction action;
    MyCharacterController character;
    // 得到唯一的实例
    void Start () {
        action = Director.getInstance().sceneController as UserAction;
    }
    //鼠标点击触发不同事件
    void OnMouseDown()
    {
        if (action.stop())
            return;
        if (gameObject.name == "boat")
        {
            action.moveBoat();
        }
        else
        {
            action.characterIsClicked(character);
        }
    }
    //设置角色控制器
    public void setController(MyCharacterController characterCtrl)
    {
        character = characterCtrl;
    }
}

8.UserGUI的函数,调整GUI界面样式,成功与失败的两种不同结果

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Mygame;

public class UserGUI : MonoBehaviour {
    private UserAction action;
    public int status = 0; // -1表示失败,1表示成功
    GUIStyle style;
    GUIStyle style2;
    GUIStyle buttonStyle;
    public bool show = false;

    void Start()
    {
        action = Director.getInstance().sceneController as UserAction;
        style = new GUIStyle();
        style.fontSize = 15;
        style.alignment = TextAnchor.MiddleLeft;
        style2 = new GUIStyle();
        style2.fontSize = 30;
        style2.alignment = TextAnchor.MiddleCenter;
    }

    void OnGUI()
    {
        if (status == -1)
        {
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 65, 100, 50), "Gameover!", style2);   
        }
        else if (status == 1)
        {
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 65, 100, 50), "You win!", style2);  
        }
        buttonStyle = new GUIStyle("button");
        buttonStyle.fontSize = 15;
        if (GUI.Button(new Rect(Screen.width / 2 - 50, 20, 100, 50), "Restart", buttonStyle))
        {
            status = 0;
            action.restart();
        }
        if (GUI.Button(new Rect(Screen.width / 2 - 50, 80, 100, 50), "Rule", buttonStyle))
        {
            show = true;
        }
        if (show)
        {
            GUI.Label(new Rect(Screen.width / 2 + 70, 20, 100, 100), "游戏规则:\n白色正方体为牧师,黑色球体为魔鬼。\n" +
            "船只最多能容纳两人,有人在船上才可开船\n" +
            "当某一岸恶魔数量大于牧师数量,游戏失败!\n" +
            "牧师与恶魔全部渡过河流,游戏胜利!\n", style);
        }
    }
}

9.firstController利用前面所建立的类,实现场景和门面两个接口,生成游戏对象,将View与Model掌控在导演手上。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Mygame;

public class FirstController : MonoBehaviour, SceneController, UserAction {
    Vector3 water_pos = new Vector3(0, 0.5f, 0);
    Vector3 bac_pos = new Vector3(0, 0, 10);

    public CoastController leftCoast;
    public CoastController rightCoast;
    public BoatController boat;

    private MyCharacterController[] characters = null;
    private UserGUI userGUI = null;
    public bool flag_stop = false;
    //得到唯一的实例
    void Awake()
    {
        Director director = Director.getInstance();
        director.sceneController = this;
        userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
        characters = new MyCharacterController[6];
        load();
        flag_stop = false;
    }
    //初始化游戏资源,如角色,船等等
    public void load()
    {
        GameObject water = Instantiate(Resources.Load("Perfabs/water", typeof(GameObject)), water_pos, Quaternion.identity, null) as GameObject;
        GameObject bac = Instantiate(Resources.Load("Perfabs/background", typeof(GameObject)), bac_pos, Quaternion.identity, null) as GameObject;
        bac.name = "background";
        water.name = "water";
        leftCoast = new CoastController("left");
        rightCoast = new CoastController("right");
        boat = new BoatController();
        for (int i = 0; i < 3; i++)
        {
            MyCharacterController character = new MyCharacterController("priest");
            character.setPosition(leftCoast.getEmptyPosition());
            character.Oncoast(leftCoast);
            leftCoast.getOnCoast(character);
            characters[i] = character;
            character.setName("priest" + i);
        }
        for (int i = 0; i < 3; i++)
        {
            MyCharacterController character = new MyCharacterController("devil");
            character.setPosition(leftCoast.getEmptyPosition());
            character.Oncoast(leftCoast);
            leftCoast.getOnCoast(character);
            characters[i + 3] = character;
            character.setName("devil" + i);
        }
    }
    public void moveBoat()
    {
        if (boat.isEmpty())
            return;
        boat.boat_move();
        userGUI.status = check_game_over();
    }
    //判断游戏胜负
    int check_game_over()
    {   
        int left_priest = 0, left_devil = 0, right_priest = 0, right_devil = 0;
        int[] fromCount = leftCoast.getCharacterNum();
        int[] toCount = rightCoast.getCharacterNum();
        left_priest += fromCount[0];
        left_devil += fromCount[1];
        right_priest += toCount[0];
        right_devil += toCount[1];
        //获胜条件
        if (right_priest + right_devil == 6)      
            return 1;
        int[] boatCount = boat.getCharacterNum();
        //统计左右两岸的牧师与恶魔的数量
        if (!boat.get_is_left())
        {   
            right_priest += boatCount[0];
            right_devil += boatCount[1];
        }
        else
        {        
            left_priest += boatCount[0];
            left_devil += boatCount[1];
        }
        //游戏失败条件
        if ((left_priest < left_devil && left_priest > 0)|| (right_priest < right_devil && right_priest > 0))
        {       
            return -1;
        }
        return 0;           //游戏继续
    }

    public void characterIsClicked(MyCharacterController character)
    {
        //角色要上岸
        if (character.getis_onboat())
        {
            CoastController coast;
            if (!boat.get_is_left())
            { 
                coast = rightCoast;
            }
            else
            {
                coast = leftCoast;
            }
            boat.GetOffBoat(character.getName());
            character.movePosition(coast.getEmptyPosition());
            character.Oncoast(coast);
            coast.getOnCoast(character);
        }
        // 角色要上船
        else
        {                                   
            CoastController coast = character.getcoastController();
            // 船上已有两人
            if (boat.getEmptyIndex() == -1)
            {      
                return;
            }
            // 船与角色并不在同一边岸
            if (coast.get_is_right() == boat.get_is_left())   
                return;
            coast.getOffCoast(character.getName());
            character.movePosition(boat.getEmptyPos());
            character.Onboat(boat);
            boat.GetOnBoat(character);
        }
        userGUI.status = check_game_over();
    }
    //重置函数
    public void restart()
    {
        boat.reset();
        leftCoast.reset();
        rightCoast.reset();
        for (int i = 0; i < characters.Length; i++)
        {
            characters[i].reset();
        }
    }
    //游戏结束后,不能再点击产生交互信息
    public bool stop()
    {
        if(check_game_over() != 0)
            return true;
        return false;
    }
}

最后将FirstController函数拖动进main的空对象中,就可以运行游戏。
效果图如下:
效果图


由于作者水平有限,如博客有任何错误,欢迎指出并讨论,谢谢
想了解更多代码详情或课程知识,可到本人github查询
Github地址:https://github.com/dick20/3d-learning

模拟太阳系

阅读数 473

太阳系小案例--H5C3

阅读数 663

canvas绘制星球轨道

阅读数 920

canvas绘制行星轨迹

博文 来自: I_intern
没有更多推荐了,返回首页