zoukankan      html  css  js  c++  java
  • Unity经典游戏教程之:松鼠大作战2

    版权声明:

    • 本文原创发布于博客园"优梦创客"的博客空间(网址:http://www.cnblogs.com/raymondking123/)以及微信公众号“优梦创客”(微信号:unitymaker)
    • 您可以自由转载,但必须加入完整的版权声明!

    松鼠大作战游戏制作

    1. 游戏介绍 1990年,经迪士尼授权,由日本卡普空(Capcom)电视游戏公司制作的基于任天堂FC主机的电视游戏《松鼠大作战》出版发行。游戏延用迪士尼动画片《松鼠大作战》里的两只可爱花栗鼠Chip and Dale(奇奇和蒂蒂),从寻找小猫咪的委托任务开始,到摆脱肥猫陷阱与其一决高下为故事内容。可单人玩又可双人对战,可互助又可互攻,流畅度,创造力与可玩性均为同类游戏中的领军者。
      1993年,卡普空制作发行了《松鼠大作战2》再一次将经典搬到屏幕。除了制作更加精良外,故事也更加惊险刺激。
      pXQxk6.jpg

    2. 场景搭建 将场景中的图片首尾拼接,在合适的位置设置碰撞器与平台控制器
      pXlknA.md.png

    3. 主角Chip登场 将主角图片拖入层级面板上,命名为“player”,添加刚体2D组件和合适的碰撞器组件。为player设置子节点hand,添加碰撞器组件设置为触发,用于触碰箱子的判断;设置子节点foot,调整恰当的位置,用于跳跃,以及主角跳跃动画的条件判定;再设置两个节点用于主角投掷物品发射的位置。pXlIHI.md.png

    4. 主角Chip动画制作 打开动画控制器,创建主角的Idel动画,在合适的时间轴拖上相应的主角图片,重复操作将主角的动画设置好。

    5. pXGvWV.md.png

    6. 主角Chip基本动作实现 为player添加脚本组件,命名为PlayerController,主角的移动需要通过输入设备为其提供指令,并进行相应的操作,Unity中Input可以获得这些操作。horizontal与vertical均是浮点数范围为-1至1,方向与卡迪尔坐标相同,isPressjump与isPressFire均为Bool类型。通过射线的方式我们可以判断处人物是否在跳跃状态下。Chip的移动与跳跃我们通过物理运动进行操作,在FixedUpdate()下进行代码操作;

    isJump = !(Physics2D.Linecast(transform.position, foot.position, 1 << LayerMask.NameToLayer("Map")) || Physics2D.Linecast(transform.position, foot.position, 1 << LayerMask.NameToLayer("Box")));
    horizontal = Input.GetAxis("Horizontal");//上下
    vertical = Input.GetAxis("Vertical");//左右
    isPressjump = Input.GetButtonDown("Jump");//跳跃
    isPressFire = Input.GetButtonDown("Fire1");//攻击
    void FixedUpdate()
        {
            if (isControl)
            {
                if (!isDown && isPressjump && !isJump)
                {
                    if (rig.velocity.y < 1F)
                    {
                        rig.AddForce(Vector3.up * force);
                    }
                    isJump = true;
                }
                if (isDown)
                {
                    rig.velocity = new Vector3(0, rig.velocity.y);
                }
                else
                {
                   rig.velocity = new Vector3(speed * horizontal, rig.velocity.y);
                }
            }
        }
    
    1. 主角Chip攻击实现 与射击游戏的区别,在本作中,CHip是通过搬物品再投掷出去进行攻击的。这里的设想是将场景的箱子与投掷的箱子区别开来,方便判断。player下的节点hand添加脚本,在OnTriggerStay2D下调用MoveBox方法,isHandBox判断手中是否有箱子,isThrow则判断有没有进行投掷操作。这里贴出主要代码:
        public void MoveBox(Collider2D collision)
        {
            //搬箱子
            if (!isDown && collision.tag.StartsWith("Box") && isPressFire && !isHandBox && !isThrow)
            {
                Destroy(collision.gameObject);//场景中的箱子被销毁了,游戏中松鼠头顶上的箱子仅仅是外观的区别
                isHandBox = true;
                animator.SetTrigger("moveBox");
                Instantiate(movebox);
            }
        }
        private void ThrowBox()
        {
            if (isHandBox && isPressFire)
            {
                isHandBox = false;
                isThrow = true;
                GameObject o = Instantiate(throwingbox);//投掷箱子时实例化一个投掷的箱子(与场景的箱子功能不同)
                if (!isDown)
                {
                    o.transform.position = transform.Find("fireposition").position;
                }
                else
                {
                    o.transform.position = transform.Find("downfireposition").position;
                }
                o.GetComponent<ThrowingBox>().Throw(transform.localScale.x, vertical);
                StartCoroutine(ResetIsThrow());
                animator.SetTrigger("throwbox");
                Instantiate(throwbox);
            }
        }
    
    1. 主角Chip动画控制器动画控制 给出合适的条件进行动画转换,本作动作丰富,通过多次尝试,本作动作才比较连贯。pXriiF.md.png

    2. 场景箱子的制作 将箱子图片拖进层级面板中,为它设置合适的碰撞器,做成预制体备用。pXsn6s.png

    3. 投掷箱子的制作 与场景箱子类似,设置触发器,另外需要增加刚体2D组件与脚本,脚本主要用于处理与怪物接触的交互,同样也制成预制体。pvrYe1.png

    4. 其他道具的制作 其他道具有花(吃了一定的数量可以奖励生命);蜂蜜(蜜蜂怪物攻击的道具,玩家触碰会造成伤害);坚果(若主角受伤,吃到该道具会增加1点Hp值);叉子(投掷小鼠投掷的道具,玩家触碰会造成1点Hp伤害)。pvr6eI.png

    5. 一些其他的制定 在本场景的末尾添加添加合适的触发器,绑上脚本可以通过下一个场景;在悬崖处也添加一个长条的触发器,用于触发主角死亡判定并重新加载本场景。

    6. 蜜蜂怪物的制作 在层级面板中,拖进一张蜜蜂怪物的图片,添加刚体组件,和合适的碰撞器,设置为触发。为这个对象设置两个动画,一个为停止动画,一个为飞行动画。摄像机还没有看到蜜蜂时,蜜蜂处于静止状态,当蜜蜂被摄像机照到时,蜜蜂处于飞行状态。蜜蜂飞到玩家的X位置时,投掷蜂蜜道具。附上蜜蜂的物理运动时脚本代码:pvs9mR.md.pngpvyGUx.md.png

        public void FixedUpdate()
        {
            if (isHit)
            {
                rig.velocity = new Vector2(masterFlyX, masterFlyY);
            }
            else if (isRest)
            {
                rig.velocity = Vector2.zero;
            }
            else if (!isStop && !isHit)
            {
                rig.velocity = new Vector2(flyspeedx, flyspeedy);
                if (this.transform.position.x < PlayerController.instance.transform.position.x && honeyNum > 0)
                {
                    Attack();
                    honeyNum--;
                    isRest = true;
                    StartCoroutine(ResetIsRest());
                }
            }
        }
    
    1. 投叉小鼠的制作 与蜜蜂怪物类似,添加刚体组件和合适的碰撞器。为这个对象设置四个动画,停止动画,巡逻动画,攻击动画,跳跃动画。给这个对象加一个点进行射线是不是发现玩家,发现时,执行攻击逻辑播放攻击动画。这里附上部分代码:pvHJIO.md.pngpvHazd.md.png
    public void Update()
        {
            if (state == State.Stop && this.transform.position.x < cam.position.x + xoffset)
            {
                //怪物行为被激活
                state = State.Walk;
                animator.SetTrigger("Walk");
            }
            else if (this.transform.position.x < cam.position.x - xoffset || this.transform.position.y > cam.position.y + yoffset)
            {
                Destroy(this.gameObject);
            }
            Walk();
            Throw();
            Escape();
        }
    
    1. Boss制作 在层级面包板中拖入Boss的图片,组件与合适的触发器,Boss主要两种状态,添加刚体,走动状态与停下攻击状态。这里附上Boss的代码:pvqdbt.md.pngpvq55T.md.png

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public class Boss : MonoBehaviour {
      
          public float range;
          public float speed;
          public float stopTime;
          public float walkTime;
          private float dir = 1;//方向正1表示坐标向右
          private float leftrange;
          private float rightrange;
          private Rigidbody2D rig;
          private Animator ani;
          private SpriteRenderer spriterender;
          private State state = State.Stop;
          private float currentTime;
          public GameObject puke;
          public float pukeSpeed;
          public int hp;
          public int totalHp;
          private bool isFlash = false;
          public float flashTime;
          public Color flashColor;
          public Color normalColor;
          public Color dangerColor;
          public float destoryTime;
      
          public GameObject bosspuke;
          public GameObject kill;
          
      
          public enum State
          {
              Stop,
              Walk,
              Attack,
              Die
          }
      	// Use this for initialization
      	void Start () {
              rig = this.GetComponent<Rigidbody2D>();
              ani = this.GetComponent<Animator>();
              spriterender = this.GetComponent<SpriteRenderer>();
              leftrange = this.transform.position.x - range;
              rightrange = this.transform.position.x + range;
      	}
      	
      	void Update () {
      
              ChangeColor();
              ani.SetInteger("state", (int)state);
              if (state == State.Stop)
              {
                  StartCoroutine(CancelStop());
              }
              else if (state == State.Walk)
              {
                  currentTime += Time.deltaTime;
                  if (currentTime > walkTime)
                  {
                      currentTime = 0f;
                      state = State.Attack;
                  }
                  else
                  {
                      if (this.transform.position.x > rightrange)
                      {
                          transform.localScale = new Vector3(-1F, 1F, 1F);
                          dir = -1F;
                      }
                      if (this.transform.position.x < leftrange)
                      {
                          transform.localScale = new Vector3(1F, 1F, 1F);
                          dir = 1F;
                      }
                  }
              }
              else if (state == State.Die)
              {
                  ani.enabled = false;
                  this.GetComponent<Collider2D>().enabled = false;
                  Destroy(this.gameObject, destoryTime);
              }
      	}
      
          public void ChangeColor()
          {
              if (isFlash)
                  spriterender.color = flashColor;
              else
                  spriterender.color = normalColor;
              if (hp <= 1)
              {
                  spriterender.color = dangerColor;
                  if (hp <= 0)
                  {
                      state = State.Die;
                  }
              }
          }
      
          public void FixedUpdate()
          {
              if (state == State.Stop || state == State.Attack)
              {
                  rig.velocity = Vector2.zero;
              }
              else if (state == State.Walk)
              {
                    rig.velocity = new Vector2(dir * speed, 0);
              }
              else if (state == State.Die)
              {
                  rig.velocity = new Vector2(-3F,-1F);
              }
          }
      
          IEnumerator CancelStop()
          {
              yield return new WaitForSeconds(stopTime);
              state = State.Walk;
          }
      
      
          public void OnTriggerEnter2D(Collider2D collision)
          {
              if (collision.gameObject.tag == "Player" &&
                !PlayerController.instance.isFlash)
              {
                  PlayerController.instance.Hurt();
                  PlayerInfo.instance.SubHp();
              }
          }
      
          public void ChangeWalkState()
          {
              state = State.Walk;
          }
      
      
          public void Attack()
          {
              GameObject o = Instantiate(puke);
              Vector2 start = this.transform.Find("firePos").position;
              Vector2 end = GameObject.Find("player").transform.position;
              o.transform.position = start;
              Vector2 dir = end - start;
              dir = dir.normalized;
              o.GetComponent<Rigidbody2D>().velocity = dir*pukeSpeed;
              Instantiate(bosspuke);
          }
      
          public void Hit()
          {
              hp--;
              isFlash = true;
              StartCoroutine(ResetFlash());
              if (hp<=0)
              {
                  Instantiate(kill);
              }
          }
      
          IEnumerator ResetFlash()
          {
              yield return new WaitForSeconds(flashTime);
              isFlash = false;
          }
      
      }
      
    2. 游戏的大体思路就是这样,让我们看看成品的图片
      pvLuRg.png
      pvLQMj.png
      pvLKzQ.png
      pvLnJS.png
      pvLlss.png
      pvLmi8.png

    更多知识、教程、源码请进↓↓↓
    优梦创客工坊

    精品内容推送请搜索↓↓↓
    微信公众号:优梦创客

    免费直播、VIP视频请进↓↓↓
    优梦创客课堂

    游戏开发交流群↓↓↓
    游戏开发交流群

  • 相关阅读:
    P4005 小 Y 和地铁
    P1039 侦探推理
    P2766 最长不下降子序列问题
    P2312 解方程
    P2169 正则表达式
    UOJ#22. 【UR #1】外星人
    UOJ#21. 【UR #1】缩进优化
    Palindromeness CodeChef
    bzoj5392 [Lydsy1806月赛]路径统计
    997D Cycles in product
  • 原文地址:https://www.cnblogs.com/raymondking123/p/8380117.html
Copyright © 2011-2022 走看看