您的位置:首页 > 移动开发 > Unity3D

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

2016-07-11 14:12 381 查看
快要一个月没有更新了,这段时间也没闲着。今天就把这段时间学到的东西稍微总结一下。

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

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

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

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

首先是一个叫做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
4000
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 实例 教育