2D游戏拾荒者开发##
一、预支物的制作##
1.获取资源: https://pan.baidu.com/s/1qYAozV2
2.图片的截取.
3.Player和Enemy的动画制作.
1.把主角的6张图片同时拖入到Hierarchy面板中,制动创建Animator,有Controller,和对应的动画.
2.在把攻击的图片拖上去,自动创建攻击动画.同理把收到伤害的图片拖上去,得到受到伤害的动画.
Enemy敌人的制作
和Player的制作一样,但是我们在制作Enemy2的时候,可以不用从新创建一个Controller了.可以创建一个
Animator Override Controller,通过Enemy1创建Enemy2的动画,改变图片从新创建一个新的预支物就可以.
3.Floor,OutWall的Prefab的制作.
二、创建地图##
这里涉及到层的概念:我们要设计好层次关系,不然系统识别不了那个在前面.
我们吧外墙(OutWall),地板(Floor)设置为最底层(Background).
食物(friut,soda)设置为Item,敌人和Player设置为Role层.
脚本代码管理:
public class MapManager : MonoBehaviour {
public GameObject[] OutWalls;
public GameObject[] Floors;
public GameObject[] walls;
public GameObject[] foods;
public GameObject[] enemys;
public GameObject exitPrefab;
public int line = 10;
public int row = 10;
Transform mapHolder;
private List<Vector2> positionList = new List<Vector2>();//用来保存在敌人,障碍物生成区域的位置信息
private GameManger gameManager;
int minCountWall = 2;//最少内墙个数
int maxCountWall = 8;//最多内墙个数,随机生成
void Awake () {
gameManager = this.GetComponent<GameManger>();
//InitMap();
}
void Update () {
}
public void InitMap()
{
mapHolder = new GameObject("Map").transform;//穿件一个空物体用来管理生成的地图
//创建外围墙和地板
for (int i = 0; i < line; i++)
{
for (int j = 0; j < row; j++)
{
if (i == 0 || i == line - 1 || j == 0 || j == row - 1)
{
int index = Random.Range(0, OutWalls.Length);
GameObject go=Instantiate(OutWalls[index], new Vector3(i, j, 0), Quaternion.identity)as GameObject;
go.transform.SetParent(mapHolder);
}
else
{
int index = Random.Range(0, Floors.Length);
GameObject go=Instantiate(Floors[index],new Vector3(i,j,0),Quaternion.identity)as GameObject;
go.transform.SetParent(mapHolder);
}
}
}
positionList.Clear();//承储敌人,障碍物区域的信息到list里面。
for (int i = 2; i < line-2; i++)
{
for (int j = 2; j < row-2; j++)
{
positionList.Add(new Vector2(i,j));
}
}
//创建障碍物
int wallCount = Random.Range(minCountWall, maxCountWall + 1);
InstantiateItem(wallCount,walls);
//创建食物2-level*2
int foodCount = Random.Range(2, GameManger.level*2+1);//创建的敌人个数与关卡有关系
InstantiateItem(foodCount,foods);
//创建敌人level/2
int enemyCount = GameManger.level/ 2;
InstantiateItem(enemyCount,enemys);
//创建出口
GameObject go1= Instantiate(exitPrefab, new Vector2(line - 2, row - 2), Quaternion.identity) as GameObject;
go1.transform.SetParent(mapHolder);
}
private Vector2 RandomPosition()
{
int positionIndex = Random.Range(0, positionList.Count);
Vector2 pos = positionList[positionIndex];//随机拿到一个位置List里面的地点信息
positionList.RemoveAt(positionIndex);//在把这个地点信息移除,避免在同一个地点出现多个物体。
return pos;
}
private GameObject RandomPrefab(GameObject[]prefabs)
{
int index = Random.Range(0, prefabs.Length);
return prefabs[index];
}
private void InstantiateItem(int count,GameObject[] prefabs)
{
for (int i = 0; i < count; i++)
{
Vector2 pos = RandomPosition();
GameObject enemyPrefab = RandomPrefab(prefabs);
GameObject go = Instantiate(enemyPrefab, pos, Quaternion.identity);
go.transform.SetParent(mapHolder);
}
}
}
三、Player和Enemy的动画完善##
四、主角(Player)的移动逻辑##
脚本展示:
public class Player : MonoBehaviour {
public float smoothing = 1;
public float restTime = 1;
//声音文件
public AudioClip chop1Audio;
public AudioClip chop2Audio;
public AudioClip step1Audio;
public AudioClip step2Audio;
public AudioClip soda1Audio;
public AudioClip soda2Audio;
public AudioClip fruit1Audio;
public AudioClip fruit2Audio;
private float restTimer = 0;
public Animator animator;
[HideInInspector]public Vector2 targetPos = new Vector2(1, 1);//主角开始的位置
private Rigidbody2D rigibody;
private BoxCollider2D collider;
void Start () {
rigibody = GetComponent<Rigidbody2D>();
collider = GetComponent<BoxCollider2D>();
animator=GetComponent<Animator>();
}
void Update () {
rigibody.MovePosition(Vector2.Lerp(transform.position, targetPos, smoothing * Time.deltaTime));
// if (GameManger.Instance.food <= 0) return;
restTimer += Time.deltaTime;
if (restTimer < restTime) return;
float h = Input.GetAxisRaw("Horizontal");//只会返回-1,0,1
float v = Input.GetAxisRaw("Vertical");
//print(h);
//print(v);
if (h>0)
{
v = 0;
}
if (h != 0 || v != 0)
{
GameManger.Instance.ReduceFood(1);
//检测
collider.enabled = false;//设置为不可用,不然会检测到自己
RaycastHit2D hit=Physics2D.Linecast(targetPos,targetPos+new Vector2(h,v));
collider.enabled = true;
if (hit.transform == null)
{
targetPos += new Vector2(h, v);
AudioManager._instance.RandomPlay(step1Audio,step2Audio);
}
else
{
switch (hit.collider.tag)
{
case "OutWall":
break;
case "Wall":
animator.SetTrigger("Attack");
AudioManager._instance.RandomPlay(chop1Audio, chop2Audio);
hit.collider.SendMessage("TakeDamage");
break;
case "Food":
GameManger.Instance.AddFood(10);
targetPos += new Vector2(h, v);
AudioManager._instance.RandomPlay(step1Audio, step2Audio);
Destroy(hit.transform.gameObject);
AudioManager._instance.RandomPlay(fruit1Audio,fruit2Audio);
break;
case "Soda":
GameManger.Instance.AddFood(20);
targetPos += new Vector2(h, v);
AudioManager._instance.RandomPlay(step1Audio, step2Audio);
Destroy(hit.transform.gameObject);
AudioManager._instance.RandomPlay(soda1Audio, soda2Audio);
break;
case "Enemy":
break;
}
}
GameManger.Instance.OnPlayerMove();//没帧检测有没有到达终点
restTimer = 0;
}
if (GameManger.Instance.food==0||GameManger.Instance.isEnd==true)
{
this.enabled = false;
}
}
public void TakeDamage(int lossFood)
{
GameManger.Instance.ReduceFood(lossFood);
animator.SetTrigger("Damager");
}
}
五、控制敌人的移动##
代码展示:
public class Enemy : MonoBehaviour {
private Vector2 targetPosition;
private Transform player;
private Rigidbody2D rigidbody;
public float smoothing = 3;
private BoxCollider2D collider;
private Animator animator;
public int lossFood = 10;
public AudioClip attackAudio;
void Start()
{
player = GameObject.FindGameObjectWithTag("Player").transform;
targetPosition = transform.position;
rigidbody = GetComponent<Rigidbody2D>();
collider = GetComponent<BoxCollider2D>();
animator = GetComponent<Animator>();
GameManger.Instance.enemyList.Add(this);//保存敌人到集合里面
}
void Update()
{
rigidbody.MovePosition(Vector2.Lerp(transform.position,targetPosition,smoothing*Time.deltaTime));
}
public void Move()
{
Vector2 offset = player.position - transform.position;
if (offset.magnitude < 1.1f)
{
//攻击
animator.SetTrigger("Attack");
AudioManager._instance.RandomPlay(attackAudio);
player.SendMessage("TakeDamage",lossFood);
}
else
{
float x = 0;
float y = 0;
if (Mathf.Abs(offset.y) > Mathf.Abs(offset.x))//按照y轴移动
{
if (offset.y < 0)//向反向
{
y = -1;
}
else//正向
{
y = 1;
}
}
else//按照x轴移动
{
if (offset.x > 0)
{
x = 1;
}
else
{
x = -1;
}
}
//设置目标位置之前,先做检测;
collider.enabled = false;
RaycastHit2D hit = Physics2D.Linecast(targetPosition, targetPosition+ new Vector2(x, y));
collider.enabled = true;
if (hit.transform == null)
{
targetPosition += new Vector2(x, y);
}
else
{
if (hit.collider.tag=="Food"||hit.collider.tag=="Soda")
{
targetPosition += new Vector2(x, y);
}
}
}
}
}
六、游戏管理脚本(对整个游戏进行管理)##
public class GameManger : MonoBehaviour {
public static int level = 1;//当前关卡
public int food = 100;
public AudioClip dieClip;
private static GameManger _instance;
public static GameManger Instance
{
get
{
return _instance;
}
}
[HideInInspector]public bool isEnd = false;
public List<Enemy> enemyList = new List<Enemy>();
private bool sleepStep = true;//控制敌人移动的限定;
private Text foodText;
private Text failText;
private Image dayImage;
private Text dayText;
private Player player;
private MapManager mapManager;
void Awake()
{
_instance = this;
//DontDestroyOnLoad(gameObject);
InitGame();
}
void InitGame()
{
//初始化地图
mapManager = GetComponent<MapManager>();
mapManager.InitMap();
food = PlayerPrefs.GetInt("Food", 10);
//初始化UI
foodText = GameObject.Find("FoodText").GetComponent<Text>();
UpdateFoodText(0);
failText = GameObject.Find("FailText").GetComponent<Text>();
failText.enabled = false;
player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();
mapManager = GetComponent<MapManager>();
dayImage = GameObject.Find("DayImage").GetComponent<Image>();
dayText = GameObject.Find("DayText").GetComponent<Text>();
dayText.text = "Day" + " " + level;
Invoke("HideBlack", 1);
//初始化参数
isEnd = false;
enemyList.Clear();
}
void UpdateFoodText(int foodChange)
{
if (foodChange == 0)
{
foodText.text = "Food:" + food;
}
else
{
string str = "";
if (foodChange<0)
{
str = foodChange.ToString();
}
else
{
str = "+" + foodChange;
}
foodText.text = str+" " + "Food:" + food;
}
}
public void ReduceFood(int count)
{
food -= count;
UpdateFoodText(-count);
if(food<=0)
{
failText.enabled = true;
AudioManager._instance.StopBgMusic();
AudioManager._instance.RandomPlay(dieClip);
}
}
public void AddFood(int count)
{
food += count;
UpdateFoodText(count);
}
public void OnPlayerMove()//通知敌人行走,逻辑,主角走2步,敌人走一步
{
if (sleepStep == true)
{
sleepStep = false;
}
else
{
foreach (var enemy in enemyList)
{
enemy.Move();
}
sleepStep = true;
}
//检测有没有到达终点
if (player.targetPos.x==mapManager.line-2&&player.targetPos.y==mapManager.row-2)
{
PlayerPrefs.SetInt("Food", food);
isEnd = true;
//加载下一个关卡
SceneManager.LoadScene(0);
//Application.LoadLevel(Application.loadedLevel);//从新加载本关卡
}
}
void OnLevelWasLoaded()
{
//PlayerPrefs.GetFloat("level", 1);
level++;
//PlayerPrefs.SetFloat("level", level);
// InitGame();
}
void HideBlack()
{
dayImage.gameObject.SetActive(false);
}
}
七、Player对敌人,食物,和墙里面障碍物的处理##
对内墙的攻击处理:
public class Wall : MonoBehaviour {
public int hp = 2;
public Sprite damageSprite;//受到攻击的图片
public void TakeDamage()
{
hp -= 1;
GetComponent<SpriteRenderer>().sprite = damageSprite;
if(hp==0)
{
Destroy(this.gameObject);
}
}
}
八、食物的UI处理##
九、声音文件的添加##
声音文件很重要,在适当的时候,合适的位置添加声音文件.
脚本管理:
public class AudioManager : MonoBehaviour {
public float minPitch = 0.9f;
public float maxPitch = 1.1f;
public static AudioManager _instance;
public AudioSource efxSouce;
public AudioSource bgSource;
void Awake()
{
_instance = this;
}
public void RandomPlay(params AudioClip[]clips)//播放声音文件
{
float pitch = Random.Range(minPitch,maxPitch);
int index = Random.Range(0,clips.Length);
AudioClip clip = clips[index];
efxSouce.clip = clip;
efxSouce.pitch = pitch;//播放速度
efxSouce.Play();
}
public void StopBgMusic()//游戏结束时,结束背景音乐的播放
{
bgSource.Stop();
}
}