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

 

posted on 2016-04-15 23:20  Wangsta  阅读(574)  评论(0编辑  收藏  举报

导航