Unity3D 学习笔记 - Garen Pick the Balls 捡球小游戏设计

注:本游戏开发环境为Unity 3D v4.6

老师说这星期作业比较简单,所以我决定写得规整一些。

开发时间:8小时

游戏要求:

小游戏争分夺秒:随机位置生成七个球,控制主角在地图拾取七个球,十秒钟内必须完成,否则失败
具体要求:
1 随机位置在地图上生成七个球(球可以用系统自带的球体)
2 用键盘控制本课程中的角色移动,鼠标左键攻击到达打击帧时,拾取碰到的球。
3 通过Time类显示每次拾取球花费的时间

经过试验,十秒根本捡不完= =,15秒还可以。。。

首先来看看帅气万分的主角Garen哥

。。。

额,忘了穿衣服

将tex_Garen拖到transform_Garon的renderer上

。。。按90年代的标准还是挺帅的。。。

当然指的是公元3世纪90年代。。。

考虑到后面的编程中会使用到盒式碰撞器Box Collider来触发,因此在这里为Garen哥加上Box Collider和Rigidbody组件。

Box Collider的形状自行调整。

都加上去之后,新建预制GarenPrefab,保存为预制。

游戏类图设计:

1. 由于游戏模型简单,GUI就交给GameModel你了!

2. 裁判类Judge保持一个列表,列表中保存着此时Garen“身旁”的球对象

  2.1 每个球自带触发器,当Garen的盒式碰撞器进入了球的触发范围的时候(OnTriggerEnter()),球触发器发出一个信息。裁判类Judge接收到这个信息,把这个球对象加入到自己的列表当中。

  2.2 Garen离开(OnTriggerExit())时,球触发器发出信息。Judge将这个球从他的列表中移除。

  2.3 Garen攻击时,发出一个信息。Judge收到这个信息,通知球工厂BallFactory将自己列表中所有的球回收。

  2.4 GarenController, Ball, Judge之间的通信,采用观察者模式,C#中利用Delegate,event实现

3. 本设计采用Legacy动画,GarenController中同时实现输入采集和动画。不得不指出Legacy动画实现非常麻烦,要自己设计状态机。这里可能会有点小bug,不在此修复,下星期学了Mecanim之后用Mecanim实现。

4. 这次裁判类总算认真做裁判工作。Judge中保存了游戏中所有重要的数据。GameModel负责调用iJudgeInterface接口来控制游戏状态,计时等等。

BaseCode:

using UnityEngine;
using System.Collections;
using Com.GarenPickingBalls;

public class BaseCode : MonoBehaviour {

    // Use this for initialization
    void Start () {
        SceneController sceneController = SceneController.GetInstance ();

        Judge judge = Judge.GetInstance ();

        UnityEngine.Camera.main.gameObject.AddComponent<GameModel> ();
    }

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

    }
}

SceneController:

using UnityEngine;
using System.Collections;

namespace Com.GarenPickingBalls{

    public class SceneController : System.Object {

        public Camera mainCam;
        public GameObject Garen;

        void initScene (){
            GameObject floor = GameObject.CreatePrimitive (PrimitiveType.Plane);
            floor.name = "floor";

            Light light = new GameObject ().AddComponent<Light> ();
            light.gameObject.name = "light";
            light.type = LightType.Point;
            light.transform.position = new Vector3 (0, 2.4f, 0);

            mainCam = UnityEngine.Camera.main;
            mainCam.transform.position = new Vector3 (0.185f, 7.734f, 6.42f);
            mainCam.transform.eulerAngles = new Vector3 (54.115f, 183.6038f, 3.777f);

            GameObject GarenPrefab = Resources.LoadAssetAtPath ("Assets/Resources/Garen/GarenPrefab.prefab", typeof(GameObject)) as GameObject;
            Garen = GameObject.Instantiate (GarenPrefab, Vector3.zero, Quaternion.identity) as GameObject;
            Garen.name = "Garen";
            Garen.AddComponent<GarenController>();
        }

        //Singleton
        public static SceneController _instance;

        public static SceneController GetInstance() {
            if (_instance == null){
                _instance = new SceneController();
            }
            return _instance;
        }

        SceneController (){
            initScene ();
        }
    }

}

GameModel:

using UnityEngine;
using System.Collections;
using Com.GarenPickingBalls;

public class GameModel : MonoBehaviour {

    BallFactory ballFactory = BallFactory.GetInstance();
    iJudgeInterface iJudge = Judge.GetInstance () as iJudgeInterface;

    void OnGUI () {
        GUI.Label (new Rect(20f,20f,300f,20f), "Garon Picking Ball Game");

        if (iJudge.getGameStatus () == 0) {
            GUI.Label (new Rect(20f, 40f, 300f, 20f), "Press Space Bar To Begin");
        }

        if (iJudge.getGameStatus () == 1) {
            GUI.Label (new Rect(20f, 40f, 300f, 20f), iJudge.getRemainingTime().ToString("0") + " seconds left");
            GUI.Label (new Rect(20f, 60f, 300f, 20f), "Garon Picked Up this ball at: " + iJudge.getLastGaronHit().ToString("0.0") + " seconds");
        }

        if (iJudge.getGameStatus () == 2) {
            GUI.Label (new Rect(20f, 40f, 300f, 20f), "Lose, press spacebar to restart");
        }

        if (iJudge.getGameStatus () == 3) {
            GUI.Label (new Rect(20f, 40f, 300f, 20f), "Win, press spacebar to restart");
        }

    }

    // Update is called once per frame
    void Update () {
        if (iJudge.getGameStatus() == 0 || iJudge.getGameStatus() == 2 || iJudge.getGameStatus() == 3) {
            if (Input.GetKey(KeyCode.Space)){
                for (int i = 0; i < 7; i++) {
                    Vector3 pos = new Vector3(Random.Range(-5f,5f), 0, Random.Range(-5f,5f));
                    GameObject ball = ballFactory.getNewBall(pos);
                }

                iJudge.startGame();
            }
        }

        else if (iJudge.getGameStatus() == 1) {
            iJudge.stepTime();

            if (iJudge.getRemainingTime() < 0){
                iJudge.loseGame();
            }

            else if (iJudge.getBallPickedUp() == 7){
                iJudge.winGame();
            }
        }
    }
}

Judge:

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

public delegate void onHitActionHandler ();
public delegate void onGaronInContactWithBall (GameObject ball);
public delegate void onGaronLeavingTheBall (GameObject ball);

public interface iJudgeInterface {
    int getGameStatus();

    void startGame();

    void loseGame();

    void winGame();

    float getLastGaronHit();

    float getRemainingTime ();

    void stepTime ();

    int getBallPickedUp ();
}

public class Judge : System.Object , iJudgeInterface {

    List <GameObject> ballInContact;
    BallFactory ballFactory;

    float TimeLimit = 15f;
    float t = 0f;

    float lastGaronHit = 0f;

    int ballPickedUp = 0;

    //game status: 0 for init, 1 for gaming, 2 for win, 3 for lose;
    int gameStatus = 0;

    //getting message from ball OnTriggerEnter()
    void GaronEnterContactWithBall (GameObject withBall){
        ballInContact.Add (withBall);
    }

    //getting message from ball OnTriggerExit()
    void GaronExitContacWithBall (GameObject withBall) {
        ballInContact.Remove (withBall);
    }

    //getting message from Garon onHit()
    void GaronHit (){
        if (ballInContact.Count != 0){
            for (int i = 0; i < ballInContact.Count; i++) {
                ballFactory.releaseBall(ballInContact[i]);
            }

            ballPickedUp += ballInContact.Count;

            lastGaronHit = t;

            ballInContact.Clear ();
        }
    }

    public int getGameStatus (){
        return gameStatus;
    }

    public void startGame(){
        t = 0f;
        lastGaronHit = 0f;
        ballPickedUp = 0;
        gameStatus = 1;
    }

    public void winGame() {
        gameStatus = 3;
    }

    public void loseGame() {
        gameStatus = 2;
        ballFactory.recycleAllBalls ();
    }

    public float getLastGaronHit() {
        return lastGaronHit;
    }

    public float getRemainingTime (){
        return TimeLimit - t;
    }

    public void stepTime () {
        t += Time.deltaTime;
    }

    public int getBallPickedUp () {
        return ballPickedUp;
    }

    //Singleton
    public static Judge _instance;

    public static Judge GetInstance() {
        if (_instance == null){
            _instance = new Judge();
        }
        return _instance;
    }

    Judge() {
        ballInContact = new List<GameObject> ();
        ballFactory = BallFactory.GetInstance ();

        GarenController.onHit += GaronHit;
        ballTrigger.enterContact += GaronEnterContactWithBall;
        ballTrigger.exitContact += GaronExitContacWithBall;
    }
}

GarenController:

using UnityEngine;
using System.Collections;
using Com.GarenPickingBalls;

public class GarenController : MonoBehaviour {

    private iJudgeInterface iJudge = Judge.GetInstance () as iJudgeInterface;

    private float speed = 3f;
    private float angularSpeed = 10f;

    private Animation animation;
    private AnimationState run;
    private AnimationState idle;
    private AnimationState attack;

    private bool attackingFlag = false;

    public static event onHitActionHandler onHit;

    // Use this for initialization
    void Start () {

        animation = GetComponent<Animation> ();

        run = animation["Run"];
        idle = animation["Idle"];
        attack = animation["Attack1"];

        idle.wrapMode = WrapMode.Loop;

        AnimationEvent aev = new AnimationEvent ();
        aev.functionName = "attackOnHit";
        aev.time = 0.3f;
        attack.clip.AddEvent (aev);

        AnimationEvent finishedAtt = new AnimationEvent ();
        finishedAtt.functionName = "finishedAttack";
        finishedAtt.time = attack.clip.length / 2f;
        attack.clip.AddEvent (finishedAtt);

        Idle ();
    }

    // Update is called once per frame
    //Some bugs in here, gonna use new mode next time anyway...
    void Update () {
        if (iJudge.getGameStatus () == 1) {
            if (Input.GetKey (KeyCode.A)) {
                transform.position += Vector3.right * speed * Time.deltaTime;
                transform.rotation = Quaternion.LookRotation (Vector3.RotateTowards(transform.forward, Vector3.right, angularSpeed * Time.deltaTime, 0.0f));
                Run ();
            }

            if (Input.GetKey (KeyCode.D)){
                transform.position += Vector3.left * speed * Time.deltaTime;
                transform.rotation = Quaternion.LookRotation (Vector3.RotateTowards(transform.forward, Vector3.left, angularSpeed * Time.deltaTime, 0.0f));
                Run ();
            }

            if (Input.GetKey (KeyCode.S)){
                transform.position += Vector3.forward * speed * Time.deltaTime;
                transform.rotation = Quaternion.LookRotation (Vector3.RotateTowards(transform.forward, Vector3.forward, angularSpeed * Time.deltaTime, 0.0f));
                Run ();
            }

            if (Input.GetKey (KeyCode.W)){
                transform.position += Vector3.back * speed * Time.deltaTime;
                transform.rotation = Quaternion.LookRotation (Vector3.RotateTowards(transform.forward, Vector3.back, angularSpeed * Time.deltaTime, 0.0f));
                Run ();
            }

            if (!Input.anyKey && !attackingFlag) {
                Idle();
            }

            if (Input.GetMouseButtonDown(0)) {
                attackingFlag = true;
                Attack();
            }
        }
        else{
            Idle ();
        }
    }

    public void Idle (float speed = 1f) {
        idle.speed = speed;
        idle.wrapMode = WrapMode.Loop;
        animation.CrossFade (idle.clip.name);
    }

    public void Run (float speed = 2f) {
        run.speed = speed;
//        run.wrapMode = WrapMode.Loop;
        animation.CrossFade (run.clip.name);
    }

    public void Attack (float speed = 1f) {
        attack.speed = speed;
        animation.CrossFade (attack.clip.name);
    }

    public void finishedAttack (){
        attackingFlag = false;
    }

    //Added to Animation Events, sending message out to delegate notifying Garon has hit
    public void attackOnHit (){
        onHit ();
    }

}

BallFactory & ballTrigger

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

public class BallFactory : System.Object {

    List<GameObject> usedBalls;
    List<GameObject> usingBalls;
    int ballCount = 0;    

    public GameObject getNewBall(Vector3 position){
        GameObject aBall = null;

        if (usedBalls.Count == 0){
            aBall = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            aBall.transform.localScale = new Vector3 (0.5f, 0.5f, 0.5f);
            aBall.transform.position = position;
            aBall.name = "ball" + ballCount.ToString();
            ballCount++;

            Collider bc = aBall.GetComponent<Collider>();
            bc.isTrigger = true;

            aBall.AddComponent<ballTrigger>();

            usingBalls.Add(aBall);
        }
        else {
            aBall = usedBalls[0];
            usedBalls.Remove(aBall);
            usingBalls.Add (aBall);
            aBall.SetActive (true);
            aBall.transform.position = position;
        }

        return aBall;
    }

    public void releaseBall (GameObject ballToRelease){

        ballToRelease.SetActive (false);
        usingBalls.Remove (ballToRelease);
        usedBalls.Add (ballToRelease);

    }

    public void recycleAllBalls (){

        while (usingBalls.Count != 0) {
            releaseBall(usingBalls[0]);
        }

    }

    //Singleton
    public static BallFactory _instance;

    public static BallFactory GetInstance() {
        if (_instance == null){
            _instance = new BallFactory();
        }
        return _instance;
    }

    BallFactory (){
        usedBalls = new List<GameObject> ();
        usingBalls = new List<GameObject> ();
    }
}

public class ballTrigger : MonoBehaviour{

    public static event onGaronInContactWithBall enterContact;
    public static event onGaronLeavingTheBall exitContact;

    //notifying that Garon has entered the trigger zone
    void OnTriggerEnter (Collider other){
        //delegate
        enterContact (this.gameObject);

    }

    //notifying that Garon has exited the trigger zone
    void OnTriggerExit  (Collider other) {
        //delegate
        exitContact (this.gameObject);
    }
}

。。。

XDDDD

时间: 2024-10-18 09:38:25

Unity3D 学习笔记 - Garen Pick the Balls 捡球小游戏设计的相关文章

Unity3D 学习笔记 - Garen Pick the Balls 捡球小游戏设计 (二) Macanim 动画状态机

注:本游戏开发环境为Unity3D 5.3.4 本星期要求: 模仿 AnimationEvent 编写一个 StateEvents 类 用户可以创建一个指定时间.指定状态触发的事件类 事件可以采用反射机制,调用调用客户的方法:或使用订阅发布方法调用客户的方法. 在你的动画控制程序中使用 StateEvents 类 我采用的是上星期的Garen Pick the Balls小游戏,将Legacy动画部分用Mecanim重写. 要点: 1. 初次状态机开发,尚未实现Run和Attack同时进行(Bl

unity3d学习笔记(十九)--ngui制作3d人物头顶的头像和血条

原地址:http://blog.csdn.net/lzhq1982/article/details/18793479 本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢. http://blog.csdn.net/lzhq1982/article/details/18793479 先上张图,自己做的一个demo. 这里的人物头像和血条是在3d世界生成的,所以有真正的纵深感和遮挡关系,废话不多说,看我是怎么实现的. 第一步,先在UI Root里制作头像和血条. 这个制作步骤基

Unity3d 学习笔记(-) Monobehaviour

从今天起开始正式学习Unity3d!!!! 下面记录Monobehaviour相关内容. Monobehaviour执行顺序,图示很清晰,简单明了,可以通过此图洞悉协程(coroutine)的运行机制. Unity3d 学习笔记(-) Monobehaviour

qml自学笔记------自己写类似于劲舞团的按键小游戏(中)

接上篇<qml自学笔记------自己写类似于劲舞团的按键小游戏(上)> 第三部分DisplayPart.qml 代码的其他部分都是渣,就这里花了点时间,整个小游戏就靠这个文件. 首先,屏幕上要随机的滑过空格或者箭头,每一个图片就是一个项目,那么就要动态的创建项目.动态创建项目方法有三种(我所知道的),第一种是通过JavaScript调用Qt.createComponent(),Qt.createQmlObject()来创建对象,这里要注意的是创建时必须传父对象,因为图形项目没有父对象是无法显

qml自学笔记------自己写类似于劲舞团的按键小游戏(下)

接上篇<qml自学笔记------自己写类似于劲舞团的按键小游戏(中)> 第四部分 PauseButton.qml 和 RestartButton.qml 第四部分其实就是两个按键,一个是暂停,一个是重新开始. 暂停按键按下时就将Timer定时器的running属性设成false,将按键上的text属性设成"开始",就这么两件事.但是,由于点击重新开始时暂停按键也需要有所改变,换而言之即其他对象要改变这个按键的属性,因此给按键添加了两个属性stat和pauseText,st

Unity3D学习笔记之七创建自己的游戏场景

到现在为止我们已经拥有了比较完备的Prefab,已经可以创建宏大的游戏场景,并以第一人称视角在场景中漫游了.这里给大家做个小的示范,建一个小场景大家在创建场景的时候需要自由发挥,做个尽量大的场景出来. 这一系列教程以及素材均参考自人人素材翻译组出品的翻译教程<Unity游戏引擎的基础入门视频教程>,下载链接附在第二篇学习笔记中. 我们以最初的添加了First Person Controller的PFB_Straight为整个场景的中心点来展开.我们先从Project中Prefabs文件夹拖出来

Unity3D学习笔记之八为场景添加细节(一)

这一系列教程以及素材均参考自人人素材翻译组出品的翻译教程<Unity游戏引擎的基础入门视频教程>,下载链接附在第二篇学习笔记中. 我花了30分钟做了一个中等大小的迷宫场景,不知道大家自己发挥,做的场景大小如何. 在完成场景之后,我们看到Hierarchy视图里面的东西已经满了,所以我们先来整理一下Hierarchy视图.创建一个空的游戏物体命名为Environment. 然后来到Hierarchy视图,先讲First Person Controller找到,挪到最上方,然后选中第一个物体,按住

Unity3D学习笔记九为场景添加细节(二)

上节为场景中添加了第一块带有碰撞器的石头,本节我们来利用Prefab,将场景细节都添加进去,并且做的更完善. 这一系列教程以及素材均参考自人人素材翻译组出品的翻译教程<Unity游戏引擎的基础入门视频教程>,下载链接附在第二篇学习笔记中. 继续来到那块已经成形的石头--Boulder旁边,我们可以制作很多石头的Prefab,这样对于丰富我们巨大的场景很有利,我们先来创建第一个石头的Prefab按下Control+D复制一块石头,按住坐标轴拖动出来,按下R调整到缩放工具,按住中心的白色立方体缩小

一步一部学习Unity3d学习笔记系1.3 英雄联盟服务器集群架构猜想

说到了网游那就涉及到服务器了,时下最火的属英雄联盟了,我也是它的粉丝,每周必撸一把,都说小撸怡情,大撸伤身,强撸灰飞烟灭,也告诫一下同仁们,注意身体,那么他的服务器架构是什么呢,给大家分享一下, 具体的是什么架构,因为没有源码,也不知道怎么回事,只能根据当前一些经验,还有撸的时候的体验猜想出来的.和实际有偏差,大家勿喷在这里只是分享,和实际的也应该相差不大. 英雄联盟服务器其实就是一个单服,单服下面有一些集群,有用户服务器,的用户服务器实际上就是QQ用户服务器QQ用户也是有服务器集群组成 客户端