2017-03-06 15:35:33 linshuhe1 阅读数 3283
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4631 人正在学习 去看看 张刚

前言:

在我们进行游戏战斗场景开发时,常常为了加强临场感,会在有爆炸或者撞击的时候加入震屏的效果,原理其实很简单,就是对场景主相机进行坐标和角度的变化。


设计

在开始进行代码实操之前,我们先考虑一下大致的思路,主要操作的参数有震动的幅度(Transform的各项参数,通常只改变localPosition和localRotation,必要的时候也可以操作localScale),还有震动的时间,假设无需规则震动,可以考虑通过在振幅范围内取随机数的方法来实现。


实现:

1.创建类,带参数构造方法:

    /// <summary>
    /// 创建振动器
    /// </summary>
    /// <param name="timer">震动时间</param>
    /// <param name="force">震动幅度</param>
    /// <param name="delay">延迟多久震动</param>
    public CameraShaker(float timer, float force,float delay = 0){
       Timer = timer;
       Force = force;
       delayTime = delay;
       Reset();
    }

Rest()方法是用来恢复一些状态和数据信息,以便于通过CameraShaker类创建单个振动器可以反复使用。

2.获得相机的起始位置,方便震动结束之后恢复相机状态:

    m_initialPosition = Camera.main.transform.position;
    m_initialRotation = Camera.main.transform.localRotation;

3.相机震动操作:

这里我直接通过while循环来实现了,当然在MonoBehavior中也可以在Update方法中实现这样的操作,这里为了封装,所以抽离了一些MonoBehavior的特性。

    while (duration > 0)
    {
        Transform objectToMove = Camera.main.transform;
        Vector3 newOffset = new Vector3(UnityEngine.Random.Range(-Force, Force),UnityEngine.Random.Range(-Force, Force),0);
        float newRotationOffset = UnityEngine.Random.Range(-RotationAngle, RotationAngle);
        objectToMove.position = objectToMove.position - m_oldOffset + newOffset;
        objectToMove.Rotate(0, 0, -m_oldRotationOffset);
        objectToMove.Rotate(0, 0, newRotationOffset);
        m_oldOffset = newOffset;
        m_oldRotationOffset = newRotationOffset;
        duration -= Time.deltaTime;
    }

duration为震动时长,而Force为震动的幅度。

4.震动结束后恢复相机参数:

    Camera.main.transform.position = m_initialPosition;
    Camera.main.transform.localRotation = m_initialRotation;
2018-02-04 19:52:54 s1314_JHC 阅读数 829
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4631 人正在学习 去看看 张刚
这部分内容关注的是游戏性的提升,包括音效、计分等功能的实现。
传送门:
太空射击(Space Shooter)流程介绍与代码分析(下)

11.添加音效
音效分配3种,爆炸音效、发射子弹音效和背景音效。其中爆炸音效将音频文件直接拖入爆炸素材中即可,如下所示。


对于背景音效文件,我们可以拖入GameController,并勾选loop,这样在游戏开始时即可自动循环播放。


而发射子弹的音效,在PlayerControl中实现,打开代码文件。添加定义和实现代码

    public AudioClip weaponFire;    //添加音效
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space) &&Time.time > nextFireTime)
        if (Input.GetButton ("Fire1")&&Time.time > nextFireTime)
        {
            audio.PlayOneShot(weaponFire);    //播放音效
            nextFireTime = Time.time + fireRate;
            Instantiate    //shooting function
                (
                bullet,
                bulletPosition.transform.position,
                bulletPosition.transform.rotation
                    );
        }
    }
}
即可,之后将音效拖入其中即可实现。


此时,需向Playership中添加一个AudioSource,否则会报错。
12.销毁爆炸效果及计分功能
在之前的进度中,我们实现了大部分游戏功能,但是无法消除爆炸效果,如下所示。


本部分就介绍如何解决这一问题。
新建一个脚本,DestroyExplosion,写入
using UnityEngine;
using System.Collections;
public class DestroyExplosion : MonoBehaviour {
    public float lifetime;
    void Start () {
        Destroy (this.gameObject, lifetime);
    }
}
即可

之后将这一脚本拖入各个爆炸效果中,如下所示即可。


分数显示:
在4.6.9中不能像教程那样添加GUIText,因此使用一个替代的Text,来代替。GameObject——>UI——>Text添加即可。GUIText的定义与Text差不多。
例如:public GUIText scoreText  与public Text ScoreText对应即可,调用函数等操作方法是一样的。


在GameController中要构建累加分数的函数,并在外部进行调用。
其定义与累加函数如下。
    private int score;
    public void AddScore(int v)
    {
        score += v;
        scoreText.text = "Score:  " + score;
    }
即可。
分数添加完毕后,就有了一个问题,在哪个外部函数进行调用呢?只有在子弹击中陨石之后才能进行加分,因此我们在DestroyByContact中的陨石爆炸处进行调用。
  1. 在DestroyByContact中,我们新建一个GameController对象gameController,之后通过gameController调用加分函数。
  2. 在void Start()中添加一个GameObject对象gameControllerObject,将Hierarchy中的GameControl的Tag设置一下,作为查找标记,如下图所示。
  3. 如果gameControllerObject找到GameControl的Tag,将gameControllerObject.GetComponent<GameController>()赋给gameController,此时gameController即可调用GameControl中的函数。
  4. 用gameController调用AddScore函数,完成分数的添加。
上述流程变量名相似,多理解几遍即可,这也是一种常用的使用“Tag”调用函数的方法。DestroyByContact的代码如下。


using UnityEngine;
using System.Collections;
public class DestroyByCotact : MonoBehaviour {
    
    public GameObject explosion;
    public GameObject playerExplosion;
    private GameController gameController;    //步骤1,新建GameController对象
    public int score = 10;
    void Start()
    {
        GameObject gameControllerObject = GameObject.FindWithTag ("GameController");    //步骤2,查找GameControl
        if (gameControllerObject != null)
        {
            gameController = gameControllerObject.GetComponent<GameController>();    //步骤3,若步骤2查找到,赋值给gameController
        }
        if (gameControllerObject == null)
        {
            Debug.Log("could not find the gamecontroller");
        }
    }
    void OnTriggerEnter (Collider other)
    {
        if (other.gameObject.tag == "Boundary")     //if other.gameObject belongs to "Boundary",do nothing.
        {
            return;
        }
        if (other.gameObject.tag == "Player")     //if other.gameObject belongs to "Player",destroy the playership.
        {
            Instantiate (playerExplosion, this.transform.position, this.transform.rotation);
            gameController.GameOver();
        }
        Destroy (other.gameObject);
        gameController.AddScore (score);    //步骤4,进行加分
        Destroy (this.gameObject);
        Instantiate (explosion, this.transform.position, this.transform.rotation);    //destroy the asteriod
    }
}
13.GameOver之后的操作
游戏结束后的操作包括以下几个:
  1. 陨石不再产生
  2. 显示游戏结束
  3. 按某个键重新开始游戏。
本节主要介绍这几个功能的实现。
在GameControl中新建一个bool值,用以判断游戏是否结束,并添加两个文本用以实现游戏结束与重新开始的提示(即2、3的操作),定义与函数实现如下。
    private bool gameOver = false;
    public void GameOver()
    {
        gameOver = true;
        gameOverText.text = "Game Over!";
        helpText.text = "Press 'R' to Restart";
    }
之后实现陨石不再产生的操作(即1),在产生陨石函数中添加判断语句并break,如下
    IEnumerator SpawnWave()
    {
        yield return new WaitForSeconds(gameStartTime);
        while (true)
        {
            for (int i=0; i<10; i++)
            {
                Spawn ();
                yield return new WaitForSeconds (waitTime);
            }
            yield return new WaitForSeconds(waveTime);
            if(gameOver) break;    //若结束,则break
        }
    }
函数定义实现后,需要确定何时调用GameOver,我们可以在DestroyByContact中的飞机爆炸中进行调用。代码如下
if (other.gameObject.tag == "Player")     
        {
            Instantiate (playerExplosion, this.transform.position, this.transform.rotation);
            gameController.GameOver();    //12节已经建立了Tag的关系,因此 gameController可以直接调用GameContrl中的GameOver函数
        }
对于重新开始游戏,我们可以在GameControl中用Update函数进行实现,代码如下
    void Update () {
        if (gameOver && Input.GetKeyDown (KeyCode.R))
        {
            Application.LoadLevel(Application.loadedLevel);    //重新加载游戏函数
        }
至此,所有的函数功能都以实现,游戏已经可以正常运行。

总结:Space Shooter作为一个初学者很容易上手的游戏,实现起来难度不大。之后将会逐步加大难度,进行更高难度游戏的开发学习。
2017-04-16 00:21:23 H12590400327 阅读数 537
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4631 人正在学习 去看看 张刚

成品展示


打飞碟

鼠标点中飞碟后,飞碟爆炸并计分,达到500分后难度提升,1000分则胜利

游戏制作

脚本挂载与预制

注意设置脚本的执行顺序,否则可能出现创建对象为空的情况!
脚本挂载如下:

预制包括一个飞碟和一个爆炸的粒子效果
注意:要把Diskdata脚本挂到预制的Disk上

下面是爆炸粒子的设置:

游戏设计以及代码分析

在上一次牧师与魔鬼游戏的基础上,新添加了工厂模式生产飞碟。

工厂类的UML图如下:


工厂

全部类的UML图如下:


全部类

各类说明

  • 导演类:管理游戏全局状态,获取当前游戏的场景,协调各个类之间的通讯,是单实例类,不被Unity内存管理所管理
  • 界面:负责与用户交互
  • 场记:场记只需要管理出飞碟规则与管理碰撞就可以了
  • 动作管理者:被场记调用,为场景中的对象设计具体动作并执行
  • 记分员:记分员按飞碟的数据计分,记分员拥有计分规则
  • 飞碟工厂:负责生产和回收飞碟

各类具体代码
这次除了引入工厂模式,还用了单实例的模板Singleton

Singleton模板代码如下:

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

public class Singleton<T> where T : MonoBehaviour
{
    private static T instance;

    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = (T)Object.FindObjectOfType(typeof(T));
                if (instance == null)
                {
                    Debug.LogError("Can't find instance of " + typeof(T));
                }
            }
            return instance;
        }
    }
}

使用起来很简单,场景单实例要求场景中至少有一个 T 类型的 Mono 子类,然后在需要的地方,如在场记中,调用如下即可获得对应类的单实例对象:

diskFactory = Singleton<DiskFactory>.Instance;
scoreRecorder = Singleton<ScoreRecorder>.Instance;
actionManager = Singleton<RoundActionManager>.Instance;

除了飞碟工厂和记分员的代码是新写的之外,其他类如场记、动作管理者、界面都是在上一篇牧师与魔鬼游戏的代码基础上修改的,工作量非常少

  • 飞碟数据 Diskdata
    记录每个飞碟自身的数据
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskData : MonoBehaviour {
    public float size;
    public Color color;
    public float speed;
}
  • 飞碟工厂 DiskFactory
    有两个链表,分别储存使用和闲置的飞碟,可以避免飞碟的销毁和过多加载
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class DiskFactory : MonoBehaviour {

    private List<GameObject> used = new List<GameObject>();//存储正在使用的飞碟
    private List<GameObject> free = new List<GameObject>();//存储使用完了被回收的飞碟

    //颜色数组用于随机分配颜色
    private Color[] color = { Color.red, Color.green, Color.blue, Color.yellow };

    //生产飞碟,先从回收部分取,若回收的部分为空,才从资源加载新的飞碟
    public GameObject GetDisk(int ruler)
    {
        GameObject a_disk;
        if (free.Count > 0)
        {
            a_disk = free[0];
            free.Remove(free[0]);
        }
        else
        {
            a_disk = GameObject.Instantiate(Resources.Load("Prefabs/Disk")) as GameObject;
            Debug.Log(a_disk);
        }
        switch (ruler)
        {
            case 1:
                a_disk.GetComponent<DiskData>().size = UnityEngine.Random.Range(0, 6);//随机大小
                a_disk.GetComponent<DiskData>().color = color[UnityEngine.Random.Range(0, 4)];//随机颜色
                a_disk.GetComponent<DiskData>().speed = UnityEngine.Random.Range(10, 15);//不同关卡速度不同,同一关卡速度在一定范围内

                a_disk.transform.localScale = new Vector3(a_disk.GetComponent<DiskData>().size * 2, a_disk.GetComponent<DiskData>().size * 0.1f, a_disk.GetComponent<DiskData>().size * 2);
                a_disk.GetComponent<Renderer>().material.color = a_disk.GetComponent<DiskData>().color;
                break;
            case 2:
                a_disk.GetComponent<DiskData>().size = UnityEngine.Random.Range(0, 4);
                a_disk.GetComponent<DiskData>().color = color[UnityEngine.Random.Range(0, 4)];
                a_disk.GetComponent<DiskData>().speed = UnityEngine.Random.Range(15, 20);

                a_disk.transform.localScale = new Vector3(a_disk.GetComponent<DiskData>().size * 2, a_disk.GetComponent<DiskData>().size * 0.1f, a_disk.GetComponent<DiskData>().size * 2);
                a_disk.GetComponent<Renderer>().material.color = a_disk.GetComponent<DiskData>().color;
                break;
        }
        a_disk.SetActive(true);
        used.Add(a_disk);
        return a_disk;
    }

    //回收飞碟
    public void FreeDisk(GameObject disk)
    {
        for(int i = 0; i < used.Count; i++)
        {
            if(used[i] == disk)
            {
                disk.SetActive(false);
                used.Remove(used[i]);
                free.Add(disk);
            }
        }
    }
}
  • 计分员
    单纯根据飞碟大小、速度和颜色计分
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScoreRecorder : MonoBehaviour {
    private float score;

    public float getScore()
    {
        return score;
    }

    public void Record(GameObject disk)
    {
        //size越小、速度越快,分越高
        score += (100 - disk.GetComponent<DiskData>().size *(20 - disk.GetComponent<DiskData>().speed));

        //根据颜色加分
        Color c = disk.GetComponent<DiskData>().color;
        switch (c.ToString())
        {
            case "red":
                score += 50;
                break;
            case "green":
                score += 40;
                break;
            case "blue":
                score += 30;
                break;
            case "yellow":
                score += 10;
                break;
        }
    }

    public void Reset()
    {
        score = 0;
    }
}
  • 场记 RoundController
    在上次牧师与魔鬼的基础上稍作改动,增加发射飞碟和回收飞碟的方法,每秒从飞碟工厂得到一只飞碟,然后让动作管理者发射,让记分员计分,每一帧检测遗留在场景的飞碟,再让飞碟工厂回收
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum State { WIN, LOSE, PAUSE, CONTINUE, START };

public interface ISceneController
{
    State state { get; set; }
    void LoadResources();
    void Pause();
    void Resume();
    void Restart();
}

public class RoundController : MonoBehaviour, IUserAction, ISceneController {

    public DiskFactory diskFactory;
    public RoundActionManager actionManager;
    public ScoreRecorder scoreRecorder;
    private List<GameObject> disks;
    private int round;//第几个回合
    private GameObject shootAtSth;
    GameObject explosion;

    //游戏状态
    public State state { get; set; }

    //计时器, 每关60秒倒计时
    public int leaveSeconds;

    //用来计数,每秒自动发射一次飞碟
    public int count;

    IEnumerator DoCountDown()
    {
        while (leaveSeconds >= 0)
        {
            yield return new WaitForSeconds(1);
            leaveSeconds--;
        }
    }

    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.setFPS(60);
        director.currentScenceController = this;

        LoadResources();

        diskFactory = Singleton<DiskFactory>.Instance;
        scoreRecorder = Singleton<ScoreRecorder>.Instance;
        actionManager = Singleton<RoundActionManager>.Instance;

        leaveSeconds = 60;
        count = leaveSeconds;

        state = State.PAUSE;

        disks = new List<GameObject>();
    }


    void Start () {

        round = 1;//从第一关开始
        LoadResources();
    }

    void Update()
    {
        LaunchDisk();
        Judge();
        RecycleDisk();
    }

    public void LoadResources()
    {
        Camera.main.transform.position = new Vector3(0, 0, -15);
        explosion = Instantiate(Resources.Load("Prefabs/ParticleSys"), new Vector3(-40, 0, 0), Quaternion.identity) as GameObject;

    }

    public void shoot()//用户在游戏状态为开始或者继续时,才能左键射击
    {
        if (Input.GetMouseButtonDown(0) && (state == State.START || state == State.CONTINUE))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                if ((SSDirector.getInstance().currentScenceController.state == State.START || SSDirector.getInstance().currentScenceController.state == State.CONTINUE))
                {
                    shootAtSth = hit.transform.gameObject;

                    explosion.transform.position = hit.collider.gameObject.transform.position;
                    explosion.GetComponent<Renderer>().material = hit.collider.gameObject.GetComponent<Renderer>().material;
                    explosion.GetComponent<ParticleSystem>().Play();
                }
            }
        }
    }

    public void LaunchDisk()//每秒自动发射飞碟
    {
        if(count - leaveSeconds == 1)
        {
            count = leaveSeconds;
            GameObject disk = diskFactory.GetDisk(round);//从飞碟工厂得到飞碟
            Debug.Log(disk);
            disks.Add(disk);//飞碟进入场景
            actionManager.addRandomAction(disk);//让动作管理者设计轨迹
        }
    }

    public void RecycleDisk()//检查需不需要回收飞碟
    {
        for(int i = 0; i < disks.Count; i++)
        {
            if( disks[i].transform.position.z < -18)
            {
                diskFactory.FreeDisk(disks[i]);//让飞碟工厂回收
                disks.Remove(disks[i]);
            }
        }
    }



    public void Judge()//判断游戏状态,是否射中以及够不够分数进入下一回合
    {
        if(shootAtSth != null && shootAtSth.transform.tag == "Disk" && shootAtSth.activeInHierarchy)//射中飞碟
        {
            scoreRecorder.Record(shootAtSth);//计分
            diskFactory.FreeDisk(shootAtSth);//回收飞碟
            shootAtSth = null;//点击的物体重置为空,避免计分出错
        }

        if(scoreRecorder.getScore() > 500 * round)//每关500分才能进入下一关,重新倒数60秒
        {
            round++;
            leaveSeconds = count = 60;
        }

        if (round == 3) //只设计了两关, 所以赢了
        {
            StopAllCoroutines();
            state = State.WIN;
        }
        else if (leaveSeconds == 0 && scoreRecorder.getScore() < 500 * round) //时间到,分数不够,输了
        {
            StopAllCoroutines();
            state = State.LOSE;
        } 
        else
            state = State.CONTINUE;

    }

    public void Pause()
    {
        state = State.PAUSE;
        StopAllCoroutines();
        for (int i = 0; i < disks.Count; i++)
        {
            disks[i].SetActive(false);//暂停后飞碟不可见
        }
    }

    public void Resume()
    {
        StartCoroutine(DoCountDown());         //开启协程计时
        state = State.CONTINUE;
        for (int i = 0; i < disks.Count; i++)
        {
            disks[i].SetActive(true);//恢复后飞碟可见
        }
    }

    public void Restart()
    {
        scoreRecorder.Reset();
        Application.LoadLevel(Application.loadedLevelName);
        SSDirector.getInstance().currentScenceController.state = State.START;
    }

}
  • 动作管理者
    可直接看最后面一个类的定义,前面几个基本动作没有改,和上一篇牧师与魔鬼游戏一样。
    只是在最下面的RoundActionManager中加了设计飞碟路径的方法。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ISSActionCallback
{
    void actionDone(SSAction source);
}

public class SSAction : ScriptableObject
{

    public bool enable = true;
    public bool destroy = false;

    public GameObject gameObject { get; set; }
    public Transform transform { get; set; }
    public ISSActionCallback callback { get; set; }

    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }

    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }
}

public class MoveToAction : SSAction
{
    public Vector3 target;
    public float speed;

    private MoveToAction() { }
    public static MoveToAction getAction(Vector3 target, float speed)
    {
        MoveToAction action = ScriptableObject.CreateInstance<MoveToAction>();
        action.target = target;
        action.speed = speed;
        return action;
    }

    public override void Update()
    {
        this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
        if (this.transform.position == target)
        {
            this.destroy = true;
            this.callback.actionDone(this);
        }
    }

    public override void Start() {}

}

public class SequenceAction : SSAction, ISSActionCallback
{
    public List<SSAction> sequence;
    public int repeat = -1; //-1表示无限循环,0表示只执行一遍,repeat> 0 表示重复repeat遍
    public int currentAction = 0;//当前动作列表里,执行到的动作序号

    public static SequenceAction getAction(int repeat, int currentActionIndex, List<SSAction> sequence)
    {
        SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
        action.sequence = sequence;
        action.repeat = repeat;
        action.currentAction = currentActionIndex;
        return action;
    }

    public override void Update()
    {
        if (sequence.Count == 0) return;
        if (currentAction < sequence.Count)
        {
            sequence[currentAction].Update();
        }
    }

    public void actionDone(SSAction source)
    {
        source.destroy = false;
        this.currentAction++;
        if (this.currentAction >= sequence.Count)
        {
            this.currentAction = 0;
            if (repeat > 0) repeat--;
            if (repeat == 0)
            {
                this.destroy = true;
                this.callback.actionDone(this);
            }
        }
    }

    public override void Start()
    {
        foreach (SSAction action in sequence)
        {
            action.gameObject = this.gameObject;
            action.transform = this.transform;
            action.callback = this;
            action.Start();
        }
    }

    void OnDestroy()
    {
        foreach (SSAction action in sequence)
        {
            DestroyObject(action);
        }
    }
}


public class SSActionManager : MonoBehaviour
{
    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
    private List<SSAction> waitingToAdd = new List<SSAction>();
    private List<int> watingToDelete = new List<int>();

    protected void Update()
    {
        foreach (SSAction ac in waitingToAdd)
        {
            actions[ac.GetInstanceID()] = ac;
        }
        waitingToAdd.Clear();

        foreach (KeyValuePair<int, SSAction> kv in actions)
        {
            SSAction ac = kv.Value;
            if (ac.destroy)
            {
                watingToDelete.Add(ac.GetInstanceID());
            }
            else if (ac.enable)
            {
                ac.Update();
            }
        }

        foreach (int key in watingToDelete)
        {
            SSAction ac = actions[key];
            actions.Remove(key);
            DestroyObject(ac);
        }
        watingToDelete.Clear();
    }

    public void RunAction(GameObject gameObject, SSAction action, ISSActionCallback whoToNotify)
    {
        action.gameObject = gameObject;
        action.transform = gameObject.transform;
        action.callback = whoToNotify;
        waitingToAdd.Add(action);
        action.Start();
    }

}

public class RoundActionManager : SSActionManager, ISSActionCallback
{
    public RoundController scene;
    public MoveToAction action1, action2;
    public SequenceAction saction;
    float speed;


    public void addRandomAction(GameObject gameObj)
    {
        int[] X = { -20, 20 };
        int[] Y = { -5, 5 };
        int[] Z = { -20, -20 };

        // 随机生成起始点和终点
        Vector3 starttPos = new Vector3(
              UnityEngine.Random.Range(-20, 20),
              UnityEngine.Random.Range(-5, 5),
              UnityEngine.Random.Range(50, 10)
             );

        gameObj.transform.position = starttPos;

        Vector3 randomTarget = new Vector3(
             X[UnityEngine.Random.Range(0, 2)],
             Y[UnityEngine.Random.Range(0, 2)],
             Z[UnityEngine.Random.Range(0, 2)]
             );

        MoveToAction action = MoveToAction.getAction(randomTarget, gameObj.GetComponent<DiskData>().speed);

        RunAction(gameObj, action, this);
    }

    protected  void Start()
    {
        scene = (RoundController)SSDirector.getInstance().currentScenceController;
        scene.actionManager = this;
    }

    protected new void Update()
    {
        base.Update();
    }

    public void actionDone(SSAction source)
    {
        Debug.Log("Done");
    }
}
  • 界面 UserGUI
    和牧师与魔鬼游戏的UserGUI脚本几乎一样,只是把之前一些多余的地方删改了
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IUserAction
{
    void shoot();//射击动作
}

public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    private float width, height;
    private string countDownTitle;

    void Start()
    {
        countDownTitle = "Start";
        action = SSDirector.getInstance().currentScenceController as IUserAction;
    }

    float castw(float scale)
    {
        return (Screen.width - width) / scale;
    }

    float casth(float scale)
    {
        return (Screen.height - height) / scale;
    }

    void OnGUI()
    {
        width = Screen.width / 12;
        height = Screen.height / 12;

        //倒计时
        GUI.Label(new Rect(castw(2f)+20, casth(6f) - 20, 50, 50), ((RoundController)SSDirector.getInstance().currentScenceController).leaveSeconds.ToString());

        //分数
        GUI.Button(new Rect(580, 10, 80, 30), ((RoundController)SSDirector.getInstance().currentScenceController).scoreRecorder.getScore().ToString());

        if (SSDirector.getInstance().currentScenceController.state != State.WIN && SSDirector.getInstance().currentScenceController.state != State.LOSE
            && GUI.Button(new Rect(10, 10, 80, 30), countDownTitle))
        {

            if (countDownTitle == "Start")
            {
                //恢复场景
                countDownTitle = "Pause";
                SSDirector.getInstance().currentScenceController.Resume();
            }
            else
            {
                //暂停场景
                countDownTitle = "Start";
                SSDirector.getInstance().currentScenceController.Pause();
            }
        }

        if (SSDirector.getInstance().currentScenceController.state == State.WIN)//胜利
        {
            if (GUI.Button(new Rect(castw(2f), casth(6f), width, height), "Win!"))
            {
                //选择重来
                SSDirector.getInstance().currentScenceController.Restart();
            }
        }
        else if (SSDirector.getInstance().currentScenceController.state == State.LOSE)//失败
        {
            if (GUI.Button(new Rect(castw(2f), casth(6f), width, height), "Lose!"))
            {
                SSDirector.getInstance().currentScenceController.Restart();
            }
        }
    }

    void Update()
    {
        //监测用户射击
        action.shoot();
    }

}
  • 导演 SSDirector
    与上一篇牧师与魔鬼游戏一样,没有改动
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;



public class SSDirector : System.Object
{
    public static SSDirector _instance;
    public ISceneController currentScenceController { get; set; }
    public bool running { 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;
    }

    public void NextScene()
    {
        Debug.Log("抱歉,没下一个场景了");
    }
}

菜鸟一个,不好的地方欢迎大家吐槽TAT。

2016-12-03 13:16:51 qq_32165329 阅读数 559
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4631 人正在学习 去看看 张刚

随着学习的进行,今天把猴哥的Unity免费教程(不包括)学完了,在最后一节课中,主要是对飞盘射击案例的功能的增添和优化。如游戏结束之后的操作:停止倒计时、销毁飞盘、显示总分、分数清零、重新开始游戏。代码并不复杂,需要注意的地方是,通过父物体查找所有的子物体的时候,会把父物体本身已返回到数组当中,为第一个元素。所以销毁子物体的时候要适当注意。

本游戏设计思路:
1、角色控制:包括武器的移动,ray的控制,旋转控制,激光的发射
2、飞盘的控制:包括飞盘的随机生成与最后的销毁,飞盘的添加刚体坠落
3、游戏UI界面的规划:包括游戏开始,游戏进行,游戏结束。
4、游戏管理器这是最重要的一部分,负责游戏主要逻辑的控制,其实也就是对上面三个元素的整合,最终完成游戏的效果。包括了界面的切换,调用角色、飞盘等的方法。


注:Unity中涉及到通过父子关系查找物体的时候,主要是通过Transform组件的方法来实现的

2014-09-15 16:36:11 jiangyongyuan 阅读数 126
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4631 人正在学习 去看看 张刚

Unity3D学习笔记——组件之Effects(效果/特效)——Particle System(粒子系统) - 有情怀的人

Effects: 效果/特效。

Particle System: 粒子系统。可用于创建烟雾、气流、火焰、涟漪等效果。

在Unity3D 3.5版本之后退出了新的shuriken粒子系统: 

 

添加组件之后的效果: 

   

其中的Open Editor按钮可以打开粒子编辑器,用于编辑复杂的粒子效果。

由于shuriken粒子系统是模块化的管理方式,所以可以动态的添加模块:

1.初始化模块:此模块是效果组件固有的模块。

Duration:粒子发射器,发射粒子的时间。单位为S(秒)。

Looping:是否开启。如果开启了循环,Duration值只要大于最小值0.10即可。

Prewarm:是否开启预热。只有在开启的循环的时候,预热才有效果,粒子量相似发射了一个粒子周期。

Start Delay:预热延迟。游戏开始多长时间后,预热开启。单位为S。 

Start Lifetime:粒子从发射器出来,到消失的时间。单位为S。 

Start Speed:粒子发射的速度。

Start Size:粒子大小。

Start Rotation:粒子的旋转角度。

Start Color:粒子的颜色。

Gravity Multiplier:设置重力对粒子的影响,数值越大影响越大。

Inherit Velocity:速度继承。当粒子系统是运动的时候,粒子的速度会继承运动的速度。但粒子坐标系必须在世界坐标系。 

Simulation Space:坐标系是本身还是世界坐标系。

Play On Awake:在游戏开始播放,但不影响Start Delay效果。

Max Particles:粒子释放的最大数量,当达到最大数量时,停止释放粒子,当有粒子消失时继续释放。

2.Emission Module(粒子发射模块)用于粒子发射的速率。或是某个特定时间发射大量的粒子,用于模拟爆炸的效果。

每秒发射粒子数量。Bursts为某个时间点爆发出粒子的数量,这个时间必须在粒子Duration范围内。

每米发射粒子数量。粒子发射器所在的游戏对象,移动的时候每米发射的粒子,但粒子坐标系必须在世界坐标系。

3.Shape Model(形状控制模块):定义了粒子发射器的形状,位置及发射方向。

球形粒子发射器:

Radius:球形的半径。

Emit from Shell:是否从表面发射粒子,还是从内部发射。

Random Direction:是否启用随机速度。

半球发射器:

锥体发射器:

Angle:椎体,上边开口的大小。

Radius:半径的大小。 

Length:椎体的高度。只有当Emit from 值为Volume Shell或是Volume时可用。

Emit From:粒子发射的位置。

——————Base:粒子发射源在椎体的内部的底面上,由底面开始发射。

——————Base Shell:粒子发射源在椎体底面的边缘,就是周长那一圈发射。

——————Volume:粒子发射源在椎体内部空间。

——————Volume Shell:粒子发射器在椎体整个表面上。没有底面。

立方体发射器:

Box X:立方体长度。

网格发射器:

Mesh:选择网格样式。

——————Vertex:粒子将从网格顶点发射。

——————Edge:粒子将从网格边缘(棱)发射。

——————Triangle:粒子将从网格的三角面发射。图形都是三角形组成的。

4.生命周期速度模块:控制每一个粒子的速度。

5.生命周期速度限制模块:

Separate Axis:是否启用限制每一个轴。

Speed:限制的速度。

Dampen:阻尼。阻尼为1的时候表示在生命周期结束的时候,速度降到限定的速度。

6.生命周期作用力模块:控制每一个粒子在生命周期内受到力的情况。

Randomize: 只有在 Random Between Two Constants或Random Between Two Curves时才可启用。

表示每一帧作用在粒子上的力是均匀随机产生的。 

7.生命周期颜色模块:控制每一个粒子在生命周期内颜色的变化。

8:速度范围颜色变化控制模块:根据设置速度的范围和粒子的速度来改变粒子的颜色。

9:生命周期粒子大小模块:控制每个粒子在生命周期内,大小的变化。

10: 速度范围粒子大小变化控制模块:根据速度的变化改变粒子的大小。

 

11:生命周期每个粒子的旋转速度:每秒粒子旋转的角度。

12:根据速度变化改变粒子的旋转速度:旋转速度不为固定常数时。

13:外部作用力倍增数:调整风对粒子的影响情况。

14:碰撞模块:为粒子创建粒子碰撞效果,目前只支持平面碰撞。

平面碰撞只支持6个平面,点击+号可以添加现在有的平面或是新建立一个。

通过新建立了碰撞平面,会成为粒子物体的子物体。

  Visualization:碰撞平面的显示方式。

——————Grid:  

——————Solid: 

Scale Plane:碰撞平面的大小。

Dampen:阻尼系数。粒子速度撞击损耗程度。0~1。

Bounce:反弹系数。0~2.系数越大,反弹角度越小。

Min kill Speed:最小销毁速度。当速度小于这个值的时候,粒子消失。

Particle Radius:粒子碰撞半径。最小值为0.01。

世界碰撞:

Collides With:碰撞层级。选择与那一层级碰撞。

Collision Quality:碰撞质量。

——————High: 每个粒子会每帧与场景做一次射线碰撞检测,需要注意的是,这样会增加CPU的负担,故在此情况下整个场景中的粒子数应当小于1000。

——————Medium: 粒子系统在每帧会受到一次Parude Raycast Budget全局设定的影晌。

——————Low: 与 中等效果相似 ,只 是粒子系统每4帧才受一次Parude Raycast Budget全局参数的影晌。

 —————— Voxel Size:碰撞缓存中的体素的尺寸,仅当Collision Quality为Medium和Low时可用。

15:子粒子发射模块:在粒子出生,碰撞,消灭时可以调用其他粒子。

   

  

  16:序列帧动画纹理模块:

Tiles:x水平分割的份数,y垂直分割的份数。

Animation:Whole Sheet-整个页面即X,Y一起移动。

Frame over Time:生命周期内,动画变幻的速率。

Cycles:生命周期内变化几次动画。

Animation:Single Row-从左到右行滚动。

Random Row:随机行。

Row:选择某一行,开始滚动,小于Tiles中的Y。

17:粒子的渲染模块:

Render Mode:渲染模式。

——————Billboard:面板渲染。

——————Vertical Billboard垂直渲染,此模式当面对摄像机时,粒子将与zX平面对齐

——————Horizontal Billboard :水平模式,此模式下粒子将沿Y轴对齐。

——————Stretched Billboard:拉伸渲染。

————C ameraScale:相机缩放。摄像机的速度对于粒子伸缩影晌的程度。speed Scale:通过比较粒子的速度决定粒子的长度。 LengthScale:通过比较粒子的宽度决定粒子的长度。

——————Mesh:模式。

18:属性:

Resimulate:实时渲染。在改变参数的时候,场景中的粒子效果实时变化。

Wireframe:选择时,将显示粒子的片面网格。

Trail Renderer

Line Renderer

Lens Flare

Halo

Projector

Legacy Particles

没有更多推荐了,返回首页