跳跳涂鸦——向上跳动游戏(学习笔记)

跳跳涂鸦跳跳涂鸦——向上跳动游戏(学习笔记)

一、需求分析

(一)、游戏的主要玩法

1、是一款2D向上跳动游戏

  • 主要玩法:player向上跳动,然后有五种类型的砖块,每个砖块有不同的属性、砖块上面有怪物,也有相应的道具,吃了道具有不同的效果。
  • 附加玩法:通过吃金币来购买皮肤
  • 拓展玩法:死亡后花钱复活,可以花钱买道具、总之可以在核心晚上的基础上加上其他的类型。还可以添加双人模式等…

2、 五种砖块的属性

  • 第一种:普通的,就是站上去向上跳
  • 第二种、跳不上去
  • 第三种:跳上去之后让主角向上跳一次,然后就下落
  • 第四种:跳上去之后高度是第一种的1.5倍
  • 第五种:在第一种的基础之上添加左右移动,和上下移动。

3、游戏物体

  • 砖块、敌人、主角、各类UI面板

二、知识点

(一)、原理知识

1、添加专门的背景相机

  • 因为游戏人物要向上移动,所以背景会离开相机的照射范围
  • 解决方案有两个,一、添加两个相机。二、背景跟随相机移动
  • 使用添加两个相机来解决,先让背景能正常的在主相机上面显示,然后新建一个相机,位置信息跟主摄像机位置保持一直,然后设置背景的渲染层级(layer)为water,然后背景相机的culling Mask 只勾选water照射层级跟背景一样,然后再设置主相机的模式为Depth only。Culling Mask 不勾选water.

2、设置底部图片跟随相机的变动而变动长度

  • 原理:图片的宽高能转化成相机照射的宽高,然后窗口的宽高跟相机照射宽高一样,在图片不能适应屏幕比例或者相机照射窗口的时候,可以通过比例调节图片的宽高以达到跟相机照射宽高一样。

  • 这里实际上是固定了屏幕比例,然后让图片适应相机照射范围。让相机照射范围的比例,跟图片转化到屏幕比例的大小一致。通过改变图片的宽高来达到一致。

    
    

(二)、插件知识

1、使用了iTween来控制MainUI中的player跳动

  • 换装界面的动画也是使用iTween来设置的

  • GameOver面板的弹出也是通过iTween来设置的

    iTween.MoveBy(gameObject, iTween.Hash(
                "y", offsetY,
                "easeType", iTween.EaseType.easeInOutQuad,
                "loopType", iTween.LoopType.pingPong,
                "time", time
                ));
    

(三)、操作知识

1、UI面板的搭建

  • 根据需求来制定游戏面板
  • UI面板主要有Main、InGame、Pause、GameOver、Skin五个
  • 五个之间的切换有两种方式
    • 第一种:通过删除在穿件的方式来确定,和通过setactive来设置
    • 第二种:通过CanvasGroup里面的alpha来设置

2、通过简单的UI框架来控制UI之间的切换

  • 设置GUIManager来控制,通过一个List和Stack来装UI面板。
  • 通过出栈和如栈来控制显示。

(四)、代码相关

1、设置Player的弹跳跟左右移动

  • 跳弹的原理主要是用刚体里面的方法来实现,需要注意的是每次跳完之后要从小设置加速度为0

  • 通过Tag来判断是不是Player进入,是Player进入才进行弹跳

    private void OnTriggerEnter2D(Collider2D collision)
        {
            if(collision.tag == "Platform")
                Jump(1);     
        }
        public void Jump(float x)
        {
            GetComponent<Rigidbody2D>().velocity = Vector2.zero;
            GetComponent<Rigidbody2D>().AddForce(new Vector2(0, 12 * x), ForceMode2D.Impulse);
           
    
        }
    
  • 左右移动:主要是通过按键的按下来控制位移来解决,通过Player身上的位置信息来进行移动

  • 移动的时候要注意Player转向的问题,通过一个三维值来转向

    Vector3 acc = Vector3.zero;
                Vector3 diff;
                if (Input.GetKey(KeyCode.LeftArrow))
                {
                    acc.x = -0.1f;
                    transform.localScale = new Vector3(-1, 1, 1);
                }
                if (Input.GetKey(KeyCode.RightArrow))
                {
                    transform.localScale = new Vector3(1, 1, 1);
                    acc.x = 0.1f;
                }
    
                diff = Vector3.MoveTowards(transform.localPosition, transform.localPosition + acc, 0.5f * Time.time);
                diff.y = transform.localPosition.y;
                diff.z = 0;
    

2、控制Player移动到边界的问题

  • Player跳到左边界的时候让其回到右边界,同理右边也一样
  • 设置两个标志位来记录左边和右边,两个标志位的位置获得通过相机的世界坐标来确定
  • 通过跳到位置信息和两个表示为来进行判断得到是否越界
	float rightBorder;
    float leftBorder;
void Start () {

        leftBorder = Camera.main.ViewportToWorldPoint(new Vector3(0, 0)).x;
        rightBorder = Camera.main.ViewportToWorldPoint(new Vector3(1, 0)).x;       
    }
 if (diff.x < leftBorder)
            {
                diff.x = rightBorder;
            }
            if (diff.x > rightBorder)
            {
                diff.x = leftBorder;
            }
            transform.localPosition = diff;

3、设置Tile的类型和参数

  • 设置砖块类型的变换,碰撞效果等。需要添加刚体和碰撞体。
  • 通过Sprite数组来进行图片的变化,然后每个图片都是一样的效果。
  • 通过switch来进行不同砖块属性的变换

4、 控制相机跟随主角

  • 正常情况下主角移动相机跟随,但是这样个人的感觉不太好,所以设置为主角超过屏幕中间的之后相机才跟随移动

    	//当前位置
        public Transform target;
        //目标位置的初始位置
        private Vector3 velocity = Vector3.zero;
        private float dampTime = 0.5f;
        public void Update()
        {
            if (target)
            {
                //使用pos来标记相机当前查看的位置为主角的当前位置
                Vector3 pos = Camera.main.WorldToScreenPoint(target.position);
                //需要移动的位置
                Vector3 delta = target.position - Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, pos.z));
                //设置目标位置的初始位置
                Vector3 destination = transform.position + delta;
                destination.x = 0;  //默认x为0
                if (destination.y > transform.position.y)
                {
                    //SmoothDown方法 参数一:当前位置,参数二:移动距离,参数三:目标的初始位置,参数四:时间间隔
                    transform.position = Vector3.SmoothDamp(transform.position, destination, ref velocity, dampTime);
                }
            }
        }
    

5、单例模板

  • 使用单例模板来控制所以代码的单例模式
public abstract  class MonoSingleton<T> : MonoBehaviour 
    where T  : MonoBehaviour
{
    private static T m_instance;
    public static T Instance
    {
        get { return m_instance; }
    }
    protected virtual void Awake()
    {
        m_instance = this as T;
    }
}

6、随机生成砖块(GameSetting类)

  • 使用一个类来控制砖块的各种高宽属性,用编辑器拓展来进行手动赋值设置,值根据需求来变动
  • 使用对象池来管理所以的砖块生成和回收。
  • 使用权值来控制不同砖块生成的比例问题。

7、砖块的左右移动和上下移动

  • 使用一个变量来统一控制,当左右移动和上下移动的砖块达到一定位置的时候就向反方向移动。
  • 然后通过OnTriggerOnenter来设置移动。

8、对象池的设置

  • 所以的游戏物体都用对象池来生成,这里没有用到对象池模板,而是简单的设置了一下生成和回收

  • 所以游戏物体都一个自己的随机生成方法和对象池

  • 生成参数有,最大高度、最小高度、生成数量、和其他不同东西之间需要的参数

     /// <summary>
        /// 从池子取物体
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        public GameObject GetInactiveObject(ObjectType type)
        {
            switch (type)
            {
                case ObjectType.Tile:
                    return tilePool.Dequeue();           
                case ObjectType.Item:
                    return itemPool.Dequeue();         
                case ObjectType.Coin:
                    return coinPool.Dequeue();                
                case ObjectType.Enemy:
                    return enemyPool.Dequeue();                
                case ObjectType.Bullet:
                    GameObject go = bulletPool.Dequeue();
                    go.transform.position = bulletSpawnPos.position;
                    go.SetActive(true);
                    return go;            
                default:
                    return null;
            }
        }
        /// <summary>
        /// 回收物体
        /// </summary>
        /// <param name="go">游戏物体</param>
        /// <param name="type">游戏物体类型</param>
        public void AddInActiveObjectToPool(GameObject go, ObjectType type)
        {
            go.SetActive(false);
            switch (type)
            {
                case ObjectType.Tile:
                    tilePool.Enqueue(go);
                    CreateTile();
                    break;
                case ObjectType.Item:
                    itemPool.Enqueue(go);
                    break;
                case ObjectType.Coin:
                    coinPool.Enqueue(go);
                    break;
                case ObjectType.Enemy:
                    enemyPool.Enqueue(go);
                    break;
                case ObjectType.Bullet:
                    bulletPool.Enqueue(go);
                    break;
                default:
                    break;
            }
        }
    
    

9、物体的生成和单个物体的对象池

  • 比较多,而且都类似,所以只放一个上来

     void GenerateTilePool()
        {
            for (int i = 0; i < initialSize; i++)
            {
                GameObject go = Instantiate(tilePrefab, transform);
                go.SetActive(false);
                go.name = i.ToString();
                tilePool.Enqueue(go);
            }
        }
    
    GameObject go = GetInactiveObject(ObjectType.Tile);
            float rand = Random.Range(0, totalsum);
            int randNumber = SetTileByRandomNumber(rand);       
            Vector2 pos = new Vector2(Random.Range(-3.5f,3.5f),currentY);
    
  • 根据不同游戏物体的需求来具体生成,如果同一个游戏物体有几个类型就用枚举类型加witch来解决

  • 生成主要通过随机数的位置。

10、难度曲线的变化

  • 主要是增加怪物的生成数量,当然达到一定程度之后就一直保持一个参数不变化了。

11、游戏物体权值的设置

  • 游戏不同之间物体的生成比例不一样,所以使用权值来确定哪些物体生成的多哪些少
  • 每个物体都有一个权值,然后加起来是总权值,生成多的个数就多设置一些。

12、换装

  • 通过改变player身上的Sprite Renderer来控制
  • 在开始界面保存游戏物体身上的sprite Renderer信息,购买了相应的皮肤之后把购买的信息赋值给开始界面。主要通过给每一个皮肤设置一个ID来控制,然后保存该ID值来进行改变。

13、游戏音乐和音效

  • 通过两个脚本来控制。
  • 背景音乐(music manager),获取游戏物体身上的AudioScenes来进行控制
  • 游戏音效(soundManager)通过一个列表装好所有的音效,在不同的点击效果来设置播放效果。

三、遇到的问题

(一)、老师错误

1、具体的

(二)、自己的错误

1、设置相机错误

  • 描述:设置两个相机是时候不能实现效果,原本主相机和背景相机相互不干扰的,但是没有该效果,背景相机移动主相机还是能看到,但是自己设置的不行
  • 原因:是从主摄像机复制一个出来进行拍摄背景的,但是自己是新建的一个相机,所以不能达到背景相机移动不影响主相机的效果。刚开设置的时候背景相机的位置跟主相机的位置不一样,所以设置了跟随以后没有跟上。如果是新建一个相机的位置跟主相机一样的话,还是能正常显示该效果的。
  • 解决:从主相机复制来制作背景相机
  • 注意:可能是位置的问题,因为位置不一样所以不能拍摄

2、测试不同的砖块的时候没有出现图片的替换

  • 描述:同上
  • 原因:设计的时候代码多了一行,但是后面没有执行,所以没有执行数组的索引替换
  • 解决:注释多写的代码
  • 注意:有时候有些参数设置了,但是后面没有处理,于是带代码上面也使用了后面没有处理的转换,于是不能达到自己想要的效果,

3、对象池生成物体的时候转换名字的时候报错,提示类型不能转换

  • 描述:同上
  • 原因:对象池回收的时候要设置为不可见状态,而代码设置了可见状态,所以不能进行同时转换
  • 解决:把代码改回来
  • 注意:写逻辑的时候对待什么时候是false什么时候是true要判断清楚。

4、砖块上下移动的时候一直向下移动

  • 描述:同上
  • 原因:在设置参数的时候,一个是y方向的移动,变成了x方向,所以砖块一直向下移动
  • 解决:把参数改回来
  • 注意:一个是在设置的时候仔细查看值,第二个是发现错误的时候定位要准确,知道是不能向上移动,肯定是x或者y的值不对。

5、floor不能跟player发生碰撞

  • 描述:
  • 原因:使用boxcollider的时候用错了,本来要使用boxcollide2D的,自己用了boxCollider
  • 解决:修改boxCollider
  • 注意:发生碰撞检测的时候,如果达不到想要的结果,先查看rigidbody,和Collider的状态,比如Collider的is Trigger勾选等。

四、最后总结

1、总结概括

  • 该游戏主要是一款2D向上跳跃游戏,主要运用技术有对象池、使用不同的对象池和随机生成方法来控制游戏物体的产生和回收、通过简单的UI框架来控制UI的显示、通过一个UI控制类添加List和栈来控制显示和隐藏,主要是给每个面板一个ID值,需要的时候出栈不需要的时候出栈、开发过程中还有很多地方运用到了List比如不同主角、方块之间的变化也是通过改变sprite Renderer来改变的。还学习了简单的换装原理。和声音管理组件的使用。

2、制作思路扩展

  • 其实不同之间的方块也可以通过面向对象的思维来制作,给方块添加一个父类。
  • 在对象池的使用过程中,也可以使用面向对象的思想,来进行写一个对象池和生成模板来进行不同的生成。

3、反思

  • 虽然看完和制作完之后,基本原理都弄清楚了,知识点也知道了,扩展思路也有,但是如果是自己写代码的话还是写不出来。
  • 一些内置方法自己也不是很清楚用法。看来编程能力还有待提高。
  • 还有就是开发的时候,有些错误可以避免的但是还是粗心没有看清楚。
  • 还有一些知识点有点懒也没有具体的写笔记,知识笼统的概括了一下而已。代码也没有贴上来。

猜你喜欢

转载自blog.csdn.net/Momorey/article/details/85040988
今日推荐