我的Unity3D学习日记-05(官方实例2Droguelike)

快要一个月没有更新了,这段时间也没闲着。今天就把这段时间学到的东西稍微总结一下。

前段时间开始看一些实战开发的内容和教程。一些是来自于麦子学院的视频,一些来自于蛮牛教育,还有一部分官方教学视频。最终选择试着跟着官方的教程,尝试敲一次这个叫做拾荒者的官方示例。

过程中遇到了许多之前不明白的知识,所以虽然代码量不是很大,但是仍然消耗了大量的时间用于查询其中一些使用的方法和技巧。

最终当然是仿照着实例成功地将游戏做了出来(虽然其中大部分东西只要跟着敲都问题不大。)

以下我将一些自己的见解以注释的形式写入了代码中。

首先是一个叫做BoardMannager的脚本,这个脚本用于生成每张地图。由于游戏是设计为一个Roguelike类型的游戏的,那么随机生成的地图一定是必不可少的。

using UnityEngine;
using System; //为了使用其中的Serializable
using System.Collections.Generic;
using Random = UnityEngine.Random;

public class BoardMannager : MonoBehaviour {

    [Serializable]//序列化,<span style="font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 25.2px;"><span style="font-size:10px;">使得变量可以被Inspector界面获得</span></span>
    public class Count //之后用于存储随机生成最小数量和最大数量的值
    {
        public int minimum;
        public int maximum;
        public Count(int min,int max)
        {
            minimum = min;
            maximum = max;
        }
    }

    public int columns = 8;
    public int rows = 8;
    public Count wallCount = new Count(5, 9);
    public Count foodCount = new Count(1, 5);
    public GameObject exit;
    public GameObject[] floorTiles;
    public GameObject[] wallTiles;
    public GameObject[] foodTiles;
    public GameObject[] enemyTiles;
    public GameObject[] outerWallTiles;

    private Transform boardHolder;//将所有通过代码生成的对象整合在boardHolder中
    private List<Vector3> gridPositions = new List<Vector3>();//存储网格位置信息

    void InitialiseList()
    {
        gridPositions.Clear();

        for (int x = 1; x < columns-1; x++)
        {
            for (int y = 1; y < rows-1; y++)
            {
                gridPositions.Add(new Vector3(x, y, 0f));//通过二重循环获得整个网格的位置
            }
        }
    }

    void BoardSetup()//生成外墙和场景内的地面
    {
        boardHolder = new GameObject("Board").transform;
        for (int x = -1; x < columns + 1; x++)
        {
            for (int y = -1; y < rows + 1; y++)
            {
                GameObject toInstantiate = floorTiles[Random.Range(0,floorTiles.Length)];
                if (x==-1|| x==columns||y==-1||y==rows)//判断是否是外墙位置
                {
                    toInstantiate=outerWallTiles[Random.Range(0,outerWallTiles.Length)];
                }
                GameObject instance = Instantiate(toInstantiate, new Vector3(x, y, 0f), Quaternion.identity) as GameObject;//prefab实例化
                instance.transform.SetParent(boardHolder); //将实例化的对象放到board中
            }
        }
    }

    Vector3 RandomPosition() //随机获取一个地图内的位置
    {
        int randomIndex = Random.Range(0, gridPositions.Count);
        Vector3 randomPosition = gridPositions[randomIndex];
        gridPositions.RemoveAt(randomIndex);
        return randomPosition;
    }


    void LayoutObjectAtRandom(GameObject[] tileArray, int minimum, int maxmum)  //将物品在地图中实例化
    {
        int objectCount = Random.Range(minimum,maxmum+1);

        for (int i = 0; i < objectCount; i++)
        {
            Vector3 randomPosition = RandomPosition();
            GameObject tileChoice = tileArray[Random.Range(0, tileArray.Length)];
            Instantiate(tileChoice,randomPosition,Quaternion.identity);
        }
    }

    public void SetupScene(int level) //调用之前的函数,完成一张地图的生成
    {
        BoardSetup();
        InitialiseList();
        LayoutObjectAtRandom(wallTiles, wallCount.minimum, wallCount.maximum);
        LayoutObjectAtRandom(foodTiles, foodCount.minimum, foodCount.maximum) ;
        int enemyCount = (int)Mathf.Log(level,2f);
        LayoutObjectAtRandom(enemyTiles, enemyCount, enemyCount);
        Instantiate(exit, new Vector3(columns - 1, rows - 1, 0f), Quaternion.identity);
        
    }
}

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


public class GameManager : MonoBehaviour {
//GameManager脚本主要用于管理整个的逻辑运行
    public float levelStartDelay = 2f;
    public float turnDelay = .1f;
    public static GameManager instance = null;//单例设计,保证在游戏运行中永远只有一个GameManager
    public BoardMannager boardScript;
    public int platerFoodPoints = 100;
    [HideInInspector]public bool playersTurn = true;

    private Text levelText;
    private GameObject levelImage;
    private int level = 1;
    private List<Enemy> enemies;
    private bool enemiesMoving;
    private bool doingSetup;


    void Awake() {
        if (instance==null)
        {
            instance = this;
        }
        else if (instance!=null)
	            Destroy(gameObject);//单例设计部分
        DontDestroyOnLoad(gameObject);
        enemies=new List<Enemy>();
        boardScript=GetComponent<BoardMannager>();//获取boardMannager脚本
        InitGame();    
    }

    private void OnLevelWasLoaded(int index)
    {
        level++;
        InitGame();
    }

    void InitGame() {
        doingSetup = true;
        levelImage = GameObject.Find("LevelImage");
        levelText = GameObject.Find("LevelText").GetComponent<Text>();
        levelText.text = "Day " + level;
        levelImage.SetActive(true);
        Invoke("HideLevelImage", levelStartDelay); //UI部分,显示第几日、显示黑色的背景等等

        enemies.Clear();
        boardScript.SetupScene(level); //调用BoardMannager生成新地图
    
    }

    private void HideLevelImage()
    {
        levelImage.SetActive(false);
        doingSetup = false;
    }
    IEnumerator MoveEnemies( )
    {
        enemiesMoving = true;
        yield return new WaitForSeconds(turnDelay);
        if (enemies.Count==0)
        {
            yield return new WaitForSeconds(turnDelay);
        }
        for (int i = 0; i < enemies.Count; i++)
        {
            enemies[i].MoveEnemy();
            yield return new WaitForSeconds(enemies[i].moveTime);
        }
        playersTurn = true;
        enemiesMoving = false;
    }

    public void AddEnemyToList(Enemy script)
    {
        enemies.Add(script);
    }


    public void GameOver()
    {
        levelText.text = "After " + level + " days,you starved.";
        levelImage.SetActive(true);
        enabled = false; //游戏结束时显示的UI
    }
	
	void Update () {
        if (playersTurn || enemiesMoving||doingSetup)
            return;
        StartCoroutine(MoveEnemies());
	
	}
}
通过以上两段脚本,能够实现地图的生成、游戏关卡的管理、以及一些简单的GUI的显示与关闭,接下来则是用于控制角色与敌人的脚本。

首先是用于可移动物体的基类MovingObject:

using UnityEngine;
using System.Collections;

public abstract class MovingObject : MonoBehaviour {
    public float moveTime = .1f;
    public LayerMask blockingLayer;
    
    public BoxCollider2D boxCollider;
    private Rigidbody2D rb2D;
    private float inverseMoveTime;
	
	protected virtual void Start () {
        boxCollider = GetComponent<BoxCollider2D>();
        rb2D = GetComponent<Rigidbody2D>();
        inverseMoveTime = 1f / moveTime;
	}
    protected bool Move(int xDir, int yDir, out RaycastHit2D hit)//通过传入两个坐标值与碰撞检测来返回能否进行移动
    {<span style="white-space:pre">	</span>
        Vector2 start = transform.position;
        Vector2 end = start + new Vector2(xDir, yDir);

        boxCollider.enabled = false;

        hit = Physics2D.Linecast(start,end,blockingLayer);//使用射线碰撞检测
        boxCollider.enabled = true;
        if (hit.transform==null)
        {
            StartCoroutine(SmoothMovement(end));
            return true;
        }
        return false;
    }

    protected IEnumerator SmoothMovement(Vector3 end)
    {
        float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
        //sqrMagnitude用于计算向量长度的平方,其效率比计算长度高
        while (sqrRemainingDistance>float.Epsilon)//Epsilon表示一个极小的数
        {
            Vector3 newPosition = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);
            rb2D.MovePosition(newPosition);
            sqrRemainingDistance = (transform.position - end).sqrMagnitude;
            yield return null;
        }
    }

    protected virtual void AttemptMove<T>(int xDir, int yDir) //使用泛型T,在子类中可以指定不同的对象
        where T : Component
    {
        RaycastHit2D hit;
        bool canMove = Move(xDir,yDir,out hit);
        if (hit.transform==null)
        {
            return;
        }
        T hitComponent = hit.transform.GetComponent<T>();//使用泛型T作为一个公共的方法
        if (!canMove && hitComponent != null)
            OnCantMove(hitComponent);
    }

    protected abstract void OnCantMove<T>(T component)
        where T : Component;

}
接下来是继承自MovingObject的子类Player和Enemy

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class Player : MovingObject{

    public int wallDamage = 1;
    public int pointsPerFood = 10;
    public int pointsPerSoda = 20;
    public float restartLevelDelay = 1f;
    public Text foodText;
    public AudioClip moveSound1;
    public AudioClip moveSound2;
    public AudioClip eatSound1;
    public AudioClip eatSound2;
    public AudioClip drinkSound1;
    public AudioClip drinkSound2;
    public AudioClip gameOverSound;

    private Animator animator;
    private int food;
    private Vector2 touchOrigin = -Vector2.one;
	protected override void Start () {
        animator = GetComponent<Animator>();//获取Animator以便更改Trigger
        food = GameManager.instance.platerFoodPoints;//获取食物可增加的点数
        foodText.text = "Food: " + food;//更新UI
        base.Start();

	}
    private void OnDisable()
    {
        GameManager.instance.platerFoodPoints = food;
    }
    protected override void AttemptMove<T>(int xDir, int yDir)
    {//重写父类中的移动方法
        food--;//每移动一次食物值减一
        foodText.text = "Food: " + food;//更新UI
        base.AttemptMove<T>(xDir, yDir);//调用父类中的移动方法
        RaycastHit2D hit;
        if (Move(xDir,yDir,out hit))
        {
            SoundManager.instance.RandomizeSfx(moveSound1, moveSound2);//如果移动成功,播放移动音效
        }
        CheckIfGameover();
        GameManager.instance.playersTurn = false;
    }

    private void CheckIfGameover()
    {//检测游戏是否结束,如果food小于等于0则结束,显示UI播放音效
        if (food <= 0)
        {
            SoundManager.instance.PlaySingle(gameOverSound);
            SoundManager.instance.musicSource.Stop();
            GameManager.instance.GameOver();
        }

            
    }
    protected override void OnCantMove<T>(T component)
    {//重写父类中的OnCantMove,可以对墙造成伤害,后见Wall类脚本
        Wall hitWall = component as Wall;
        hitWall.DamageWall(wallDamage);
        animator.SetTrigger("PlayerChop");//通过SetTrigger播放Chop的动画
    }
    private void Restart()
    {
        Application.LoadLevel(Application.loadedLevel);
    }
    public void LoseFood(int loss)
    {//受到攻击时减少food值,播放音效,更新UI
        animator.SetTrigger("PlayerHit");
        food -= loss;
        foodText.text = "-" + loss + "Food: " + food;
        CheckIfGameover();
    }
    private void OnTriggerEnter2D(Collider2D other)
    {//碰撞物体发生的事件,如果是Exit进入下一关,如果是Food或者Soda,增加food值,播放音效更新UI
        if (other.tag == "Exit")
        {
            Invoke("Restart", restartLevelDelay);
            enabled = false; 
        }
        else if (other.tag == "Food")
        {
            food += pointsPerFood;
            SoundManager.instance.RandomizeSfx(eatSound1, eatSound2);
            foodText.text = "+" + pointsPerFood + "Food: " + food;
            other.gameObject.SetActive(false);
        }
        else if (other.tag == "Soda")
        {
            food += pointsPerSoda;
            SoundManager.instance.RandomizeSfx(drinkSound1, drinkSound2);
            foodText.text = "+" + pointsPerSoda + "Food: " + food;
            other.gameObject.SetActive(false);
        }
        
    }
	
	void Update () {
        if (!GameManager.instance.playersTurn) return;
        int horizontal = 0;
        int vertical = 0;
    #if UNITY_STANDALONE||UNITY_WEBPLAYER //多平台操控,如果是PC或者WEBPALYER可以直接使用键盘WSAD或者方向键操作
        horizontal = (int)Input.GetAxisRaw("Horizontal");
        vertical = (int)Input.GetAxisRaw("Vertical");
        if (horizontal != 0)
            vertical=0;
        
    #else
        if (Input.touchCount>0) //如果是手机端则可以使用滑动触摸屏的方法进行操作
	    {
		 Touch myTouch=Input.touches[0];
            if (myTouch.phase==TouchPhase.Began)
	    {
		    touchOrigin=myTouch.position;
	    }
            else if (myTouch.phase==TouchPhase.Ended&&touchOrigin.x>=0)
	    {
		     Vector2 touchEnd=myTouch.position;
                float x=touchEnd.x-touchOrigin.x;
                float y=touchEnd.y-touchOrigin.y;
                touchOrigin.x=-1;
                if(Mathf.Abs(x)>Mathf.Abs(y))
                    horizontal=x>0?-1:1;
                else
                    vertical=y>0?-1:1;
	    }
	}
#endif
        if (horizontal != 0 || vertical != 0)
            AttemptMove<Wall>(horizontal,vertical);
	}
}
using UnityEngine;
using System.Collections;

public class Enemy : MovingObject {
//同样继承自MovingObject类
    public int playerDamage;
    private Animator animator;
    private Transform target;
    private bool skipMove;
    public AudioClip enemyAttack1;
    public AudioClip enemyAttack2;


	protected override void Start () {
        GameManager.instance.AddEnemyToList(this);
        animator = GetComponent<Animator>();
        target=GameObject.FindGameObjectWithTag("Player").transform;
        base.Start();
    }

    protected override void AttemptMove<T>(int xDir, int yDir)
    {
        if (skipMove)
        {
            skipMove = false;
            return;
        }
        base.AttemptMove<T>(xDir, yDir);
        skipMove = true;

    }

    public void MoveEnemy()
    {
        int xDir = 0;
        int yDir = 0;
        if (Mathf.Abs(target.position.x - transform.position.x) < float.Epsilon)//先判断敌人与玩家X轴之间是否相差,如果相差先移动X方向
        {
            yDir = target.position.y > transform.position.y ? 1 : -1; //判断移动向玩家的方向

        }
        else
            xDir = target.position.x > transform.position.x ? 1 : -1; //同理的判断Y轴移动的方向
        AttemptMove<Player>(xDir, yDir); //传入泛型<Player>
    }

    protected override void OnCantMove<T>(T component)
    {
        Player hitPlayer = component as Player;
        animator.SetTrigger("enemyAttack");//播放Attack动画
        hitPlayer.LoseFood(playerDamage); //攻击使得玩家减少food值
        SoundManager.instance.RandomizeSfx(enemyAttack1, enemyAttack2);
    }
}
接下来是墙的脚本Wall
using UnityEngine;
using System.Collections;

public class Wall : MonoBehaviour {

    public Sprite dmgSprite;
    public int hp = 4;
    public AudioClip chopSound1;
    public AudioClip chopSound2;
    private SpriteRenderer spriteRenderer;
	
	void Awake () 
    {   
        spriteRenderer=GetComponent<SpriteRenderer>();
	
	}

    public void DamageWall(int loss)
    {
        SoundManager.instance.RandomizeSfx(chopSound1, chopSound2);
        spriteRenderer.sprite = dmgSprite;
        hp -= loss;
        if (hp <= 0)//每堵墙有4点生命值,当生命值为0时就会被设置为Disable
            gameObject.SetActive(false);
    }
}
using UnityEngine;
using System.Collections;
 //SoundManager类用于管理游戏中会出现的背景音乐以及音效
public class SoundManager : MonoBehaviour {
    public AudioSource efxSource;
    public AudioSource musicSource;
    public static SoundManager instance = null;

    public float lowPitchRange = .95f;
    public float highPitchRange = 1.05f;
	// Use this for initialization
	void Awake () {
        if (instance == null)
            instance = this;
        else if (instance != this)
            Destroy(gameObject);
        DontDestroyOnLoad(gameObject);
	
	}
    public void PlaySingle(AudioClip clip)
    {
        efxSource.clip = clip;
        efxSource.Play();
    }

    public void RandomizeSfx(params AudioClip[] clips)
    {
        int randomIndex = Random.Range(0, clips.Length);
        float randomPitch = Random.Range(lowPitchRange, highPitchRange);
        efxSource.pitch = randomPitch;
        efxSource.clip = clips[randomIndex];
        efxSource.Play(); 
    }

	
}
using UnityEngine;
using System.Collections;
//最后将Loader脚本放在MainCamera上,实现开始游戏
public class Loader : MonoBehaviour {
    public GameObject gameManager;
    void Awake()
    {
        if (GameManager.instance == null)
            Instantiate( gameManager);
    }
}








  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity是一款强大的游戏开发引擎,对于初学者来说,制作界面是一个很好的练习项目。首先,你需要先了解Unity的基本操作和界面布局。在学习过程中,推荐去官方网站或者YouTube上寻找相关的视频教程,这样可以更直观地了解Unity的工作流程和操作方法。 在制作界面的过程中,你可以在Unity的场景视图中对界面进行布局和设计。使用Unity提供的UI系统,你可以选择各种按钮、文本框、图片等控件,然后通过拖拽和调整的方式进行界面的布局和设计。 接着,你可以利用Unity的脚本系统给界面添加交互功能。通过编写简单的脚本,你可以实现按钮的点击事件、文本的内容更新等功能。这样就可以让你的界面更加生动和具有交互性。 当你完成了界面的设计和基本交互功能后,你可以进一步学习如何进行界面的优化和美化。比如调整UI元素的大小和位置,选择合适的颜色和字体,添加过渡效果等,来提升界面的用户体验。 最后,不要忘记在学习过程中遇到问题时,多查阅文档和求助社区。Unity拥有庞大的社区和丰富的文档资源,可以帮助你解决很多问题并且加速学习的过程。 总的来说,制作界面是一个很好的Unity初学者练习项目,通过这个过程你可以熟悉Unity的操作方法和工作流程,掌握一些基本的UI设计和交互编程技能。希望以上建议对你有所帮助,祝你学习顺利!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值