游戏介绍:玩家控制一个2D帧动画人物在地图上行走,目标是到达出口,进入下一关。途中会有食物、敌人和障碍物。玩家每进行一个动作都会销耗食物,靠近敌人会攻击玩家,可以摧毁障碍物。食物耗尽时游戏结束。
1、游戏组件
1.1、帧动画 Animation
这个游戏是简单的帧动画,美术资源已按固定大小准备好每一帧的动画。
- 选中动画的所有帧,拖动到GameObject中会生成相应的Animation文件和配套的AnimationController。
- Animation包括闲置动画、攻击动画和被攻击动画(Idle、Attack、Damage)
- 当Trigger.Attack触发时,Idle立即转为Attack,Attack播放完后回到Idle。
- 当Trigger.Damage触发时,Idle立即转为Damage,Damager播放完后回到Idle
- 通过Override Controller使不同的敌人共用一个AnimationController,但播放不同的动画。
1.2 渲染层 SpriteRenderer.SortingLayer
游戏中渲染层有Background、Items、Units(可以自己添加)。渲染层低的物体绘制比较晚,所以会遮挡渲染层高的物体。
- Backgound 放置背景(Floot、OutWall)等无需交互的物体。
- Items 放置物品(Food、Suda)等需要交互的物体。
- Units 放置角色(Player、Enemy)等会行动的角色。
1.3 行走和碰撞检测 Rigidbody and BoxCollider
添加BlockLayer层,为Player、Enemy、Wall等所有碰撞检测(BoxCollider)都将在这个层中进行。
- Player和Enemy等需要运动的角色,设置为Kinematic Rigidbody,即可以通过物理系统移动,但不会收到环境力影响。
- 将Player和Enemy等需要和环境交互的单位,其碰撞器尺寸(BoxCollider.size)设置为0.9,防止角色移动时与周围环境发生不必要的碰撞。
- 将Exit和Food等物体设置为触发器模式(IsTrigger),不会产生物理碰撞效果。
1.4 界面(UI)
界面包括DayImage和FoodText
- DayImage显示当前天数,只在玩家进入每个关卡前出现,1秒后消失。
- FoodText显示玩家当前食物数量,并在每次食物变化时出现改变的值(停留0.5秒)。
1.5 声音(AudioSource)
背景音乐循环播放。
音效包含玩家移动、攻击、受伤、拾取食物和死亡,敌人攻击等。通过AudioManager脚本统一控制。
2、代码结构
2.1 游戏管理类
2.1.1 GameManager(游戏管理类)
单例模式 Singleto
- 通过DontDestoryOnLoad(this),使自己不会在关卡开始时被销毁、重置。
- 在Awake()中进行游戏的初始化(包括重新生成地图)
- 在MainCamera上的脚本中建立Singleto。
初始化游戏 InitGame()
- 调用MapManager.InitMap()随机生成地图。
- 重新初始化当前UI。
- 清空上一关卡中的敌人List。
管理敌人 EnemyList
- 在敌人生成后加入EnemyList统一管理。
- 协程EnemyMove()让敌人按一定顺序移动。
管理地图
- OnLevelWasLoaded(int level)已停用,用OnSceneLoad(Scene scene, LoadSceneMode sceneMode)代替。
- 删除Awake()中的InitGame(),InitGame()仅在OnSceneLoad()中使用。
private void OnDisable() {
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private void OnEnable() {
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode) {
level++;
InitGame();
Debug.Log("当前第" + level + "天");
Debug.Log("饥饿:" + hunger);
}
2.1 MapManager(地图管理类)
//生成地图范围
for (int x = 2; x < cols - 2; x++) {
for (int y = 2; y < rows - 2; y++) {
positionList.Add(new Vector2(x, y));
}
}
//随机生成一个游戏交互物
private void RandomInstantatePrefab(GameObject[] prefabs) {
//随机生成位置
int positionIndex = Random.Range(0, positionList.Count);
Vector2 position = positionList[positionIndex];
positionList.RemoveAt(positionIndex);//该位置已存在物体(即将),移除该位置
//根据位置,随机生成一个一个物体
int index = Random.Range(0, prefabs.Length);
GameObject gameObject = Instantiate(prefabs[index], position, Quaternion.identity) as GameObject;
gameObject.transform.SetParent(mapHolder);
}
2.2 角色类
Player和Enemy类使用,通过RaycastHit2D检测前方的物体。
//移动
mRigidbody.MovePosition(Vector2.Lerp(transform.position, mTargetPosition, speed * Time.deltaTime)); 、
//...
mCollider.enabled = false;//防止射线击中自己
RaycastHit2D hit = Physics2D.Linecast(transform.position, mTargetPosition + new Vector2(h, v));//发射射线检测
mCollider.enabled = true;
if(hit.transform == null) { //说明前方无障碍物
//前进
} else {
switch (hit.collider.tag) {
case "Wall"://前方是障碍物
//攻击
break;
//...
}
}
2.3 其他类
- Wall类
public Sprite damageSprite;//受到攻击后的图片
//通过替换图片来达到显示破损状态
GetComponent<SpriteRenderer>().sprite = damageSprite;//收到攻击后,替换图片