快要一个月没有更新了,这段时间也没闲着。今天就把这段时间学到的东西稍微总结一下。
前段时间开始看一些实战开发的内容和教程。一些是来自于麦子学院的视频,一些来自于蛮牛教育,还有一部分官方教学视频。最终选择试着跟着官方的教程,尝试敲一次这个叫做拾荒者的官方示例。
过程中遇到了许多之前不明白的知识,所以虽然代码量不是很大,但是仍然消耗了大量的时间用于查询其中一些使用的方法和技巧。
最终当然是仿照着实例成功地将游戏做了出来(虽然其中大部分东西只要跟着敲都问题不大。)
以下我将一些自己的见解以注释的形式写入了代码中。
首先是一个叫做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); } }
我的Unity3D学习日记-05(官方实例2Droguelike)
原文:http://blog.csdn.net/axlxzero/article/details/51880334