用Unity制作小游戏 - 暗影惊吓

最近玩了一个小游戏,叫做暗影惊吓,虽然是一个十分简单的小游戏,但是感觉还是十分有趣的。这里就用Unity来实现一个类似的游戏。
项目源码DarkFollow

主要工作分析

  1. 主角的控制(重点):左右移动、跳跃、动画播放等
  2. 场景的设计:地板、空中平台、背景等
  3. 影子跟随(重点):跟随着主角的有害影子
  4. 奖励:可以加分,部分奖励会导致产生影子
  5. 震动:主角根据降落高度,碰到地面会使界面有一个震动效果

主角控制

  1. 左右移动
    根据键盘输入,来为刚体设置速度即可,同时设置Speed变量来控制动画中Idel和Run的切换

    //CharacterControl.cs
    _HorizontalInput = Input.GetAxis("Horizontal");
    ...
    //CharacterData.cs
    _Move = horizontalInput*MoveSpeed;
    _Rigid.velocity = new Vector2(_Move, _Rigid.velocity.y);
    _Animator.SetFloat("Speed", Mathf.Abs(_Move));

    同时,我们还需要决定玩家的面向,不能一直都朝向右边:

    //CharacterData.cs
    _Move = horizontalInput*MoveSpeed;
    //决定面向
        if(_Move != 0)
        {
            Vector3 oldScale = transform.localScale;
            float scaleX = _Move > 0 ? 1 : -1;
            transform.localScale = new Vector3(scaleX, oldScale.y, oldScale.z);
        }
  2. 跳跃
    首先我们要确定玩家是否站在地板上,因此我们需要首先给角色底下添加"Foot":

    然后以Foot为圆心,查看是否与地板相交:

      //CharacterData.cs
        Collider2D[] colliders = Physics2D.OverlapCircleAll(Foot.transform.position, Radius);
        _IsGrounded = false;
        if (colliders != null)
        {
            for(int i=0; i < colliders.Length; i++)
            {
                if(colliders[i].gameObject.layer == LayerMask.NameToLayer("Ground"))
                {
                    _IsGrounded = true;
                }
            }
        }

    接着,我们就需要判断玩家是否按下了跳跃键:

    //CharacterControl.cs
    if(!_IsJump && Input.GetKeyDown(KeyCode.UpArrow))
        {
            _IsJump = true;
        }

    然后,就是进行跳跃判断了:

    //CharacterData.cs
       if(jump && _IsGrounded)
        {
            _Rigid.AddForce (new Vector2(0, JumpForce));
            _Animator.SetBool("Jump", true);
        }
  3. 动画播放
    动画状态机设计如下:
  4. 边界穿越
    玩家可以走到左边界,然后从右边界出来。我们可以如下添加给左右边界均添加碰撞框:

    当玩家碰到碰撞框时,会把位置设为另外一边:

    //CharacterControl.cs
    void OnCollisionEnter2D(Collision2D coll)
    {
        Vector2 oldPos = transform.position;
        string name = coll.gameObject.name;
        //让玩家能够从左边界直接到右边界(或者相反)
        if(name == "LeftBorder")
        {
            transform.position = new Vector2(_RightBorderX, oldPos.y);
        }
        else if(name == "RightBorder")
        {
            transform.position = new Vector2(_LeftBorderX, oldPos.y);
        }
    }

影子跟随

  1. 记录影子数据
    为了生成一个影子,我们需要玩家的历史数据,其中需要位置、面向、动画状态机的切换变量,因此可得如下数据结构:

    public class ShadowData
    {
    public Vector3 Pos; //位置
    public Vector3 Scale; //缩放(用于决定面向)
    //以下都是动画状态机中的变量
    public float Speed;
    public float VerticalSpeed;
    public bool IsJump;
    
    public ShadowData(Vector3 pos, Vector3 scale, float speed, float verticalSpeed, bool isJump)
    {
        Pos = pos;
        Scale = scale;
        Speed = speed;
        VerticalSpeed = verticalSpeed;
        IsJump = isJump;
    }
    }

    然后玩家每帧都需要记录ShadowData数据:

    //CharacterData.cs
    void Update()
    {
        AddShadowData();
    }
    
    //记录阴影数据
    public void AddShadowData()
    {
        Vector3 pos = transform.position;
        Vector3 scale = transform.localScale;
        float speed = _Animator.GetFloat("Speed");
        float verticalSpeed = _Animator.GetFloat("VerticalSpeed");
        bool isJump = _Animator.GetBool("Jump");
    
        ShadowData data = new ShadowData(pos, scale, speed, verticalSpeed, isJump);
        ShadowDatas.Add(data);
    }
  2. 黑色影子
    首先我们需要一个黑色的影子,因此可创建如下的Material:

    然后把他赋值给SpriteRenderer:

    然后就能得到一个对应的黑色影子了:
  3. 会跟随的影子
    我们只要把玩家的历史数据赋值给影子就行,但是要注意,因为玩家的历史数据使用List存储的,因此越后面的数据越新。因为新生成的影子是在旧的影子后面,因此我们应该要从后面开始读数据,让旧的影子读更新的数据从而离玩家更近。而且影子和玩家要有一定距离,因此该距离之类的新数据都是暂时不能读的。如下图所示:

    可见,每个影子都应该有自己对应的Index去读。代码如下:

    public const int GAP_TO_PLAYER = 80; //第一个影子距离玩家的距离(即这数字之后的数据才能被读)
    
    public void Refresh (CharacterData player) {
        int minSize = Index + GAP_TO_PLAYER;
        if(player.ShadowDatas.Count > minSize)
        {
            this.gameObject.SetActive(true);
            int index = player.ShadowDatas.Count - 1 - GAP_TO_PLAYER - Index; //读倒数的数据
            ShadowData data = player.ShadowDatas[index];
            RefreshStateBy(data);
        }
        else
        {
            this.gameObject.SetActive(false); //防止没数据时,影子傻乎乎地站在初始位置
        }
    }
    
    void RefreshStateBy(ShadowData data)
    {
        transform.position = data.Pos;
        transform.localScale = data.Scale;
        _Animator.SetBool("Jump", data.IsJump);
        _Animator.SetFloat("Speed", data.Speed);
        _Animator.SetFloat("VerticalSpeed", data.VerticalSpeed);
    }
  4. 管理影子
    一个游戏中可能生成多个影子,因此我们需要进行管理。我们需要保证每个影子之间能有一定的距离。代码如下:

void Update () {
      for (int i = 0; i < Shadows.Count; i++)
      {
          Shadows[i].Refresh(Player);
      }

      if (Player.ShadowDatas.Count > (Shadows.Count*GAP + Shadow.GAP_TO_PLAYER) && Shadows.Count > 0)
          Player.ShadowDatas.RemoveAt(0);
}

  public void CreateShadow()
  {
      GameObject shadowGo = (GameObject)Resources.Load("Shadow");
      shadowGo = Instantiate(shadowGo);
      shadowGo.transform.parent = this.transform;
      Shadow shadow = shadowGo.GetComponent<Shadow>();
      shadow.Index = Shadows.Count * GAP; //保证每个阴影之间有一定距离
      Shadows.Add(shadow);
  }

其他

其他工作就比较简单了。

  1. 奖励
    我们只需要设置所有奖励的生成点:

    然后随机生成即可:

    public void RandomCreateBonus()
    {
    int index = Random.Range(0, Bonuses.Count);
    while(index == _PreviousIndex) //保证不生成同一位置
    {
        index = Random.Range(0, Bonuses.Count);
    }
    _PreviousIndex = index;
    Bonuses[index].CreateBonus(IsNormal());
    }

    当然,我们还需要根据设置来生成不同类型的奖励。

  2. 摄像机抖动
    根据玩家的跳跃高度来决定抖动幅度,而这里抖动是直接用协程来实现:

    IEnumerator ShakeCoroutine(float jumpDistance)
    {
        float t = Mathf.Clamp(jumpDistance / HEIGHT, 0, 1);
        float delta = Mathf.Lerp(MIN_DELTA, MAX_DELTA, t);
        MainCamera.transform.position = new Vector3(_OldPos.x, _OldPos.y + delta, _OldPos.z);
        yield return new WaitForSeconds(0.1f);
        MainCamera.transform.position = new Vector3(_OldPos.x, _OldPos.y - delta, _OldPos.z);
        yield return new WaitForSeconds(0.1f);
        MainCamera.transform.position = new Vector3(_OldPos.x, _OldPos.y, _OldPos.z);
    }
  3. 剩下
    剩下的直接查看项目源码吧。

Unity小游戏制作 - 暗影随行的更多相关文章

  1. 自制Unity小游戏TankHero-2D(2)制作敌方坦克

    自制Unity小游戏TankHero-2D(2)制作敌方坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...

  2. 自制Unity小游戏TankHero-2D(1)制作主角坦克

    自制Unity小游戏TankHero-2D(1)制作主角坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...

  3. 自制Unity小游戏TankHero-2D(5)声音+爆炸+场景切换+武器弹药

    自制Unity小游戏TankHero-2D(5)声音+爆炸+场景切换+武器弹药 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm ...

  4. 自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

    自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm ...

  5. 自制Unity小游戏TankHero-2D(3)开始玩起来

    自制Unity小游戏TankHero-2D(3)开始玩起来 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的.仅 ...

  6. 半期考html5小游戏制作

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. 手把手教学h5小游戏 - 贪吃蛇

    简单的小游戏制作,代码量只有两三百行.游戏可自行扩展延申. 源码已发布至github,喜欢的点个小星星,源码入口:game-snake 游戏已发布,游戏入口:http://snake.game.yan ...

  8. Unity引擎入门——制作第一个2D游戏(1)

    Unity作为当今最流行的游戏引擎之一,受到各大厂商的喜爱. 像是炉石传说,以及最近的逃离塔克夫,都是由unity引擎开发制作. 作为初学者的我们,虽然无法直接做出完成度那么高的作品,但每一个伟大的目 ...

  9. Kinect+unity 实现体感格斗闯关小游戏

    文章目录 项目地址 1 项目概况 1.1 项目简介 1.2 项目目的 1.3 主要技术 2 设计 2.1 基本概念 2.2 框架 2.3 算法 2.4 模型 2.5 调查问卷 3 实现 3.1 技术难 ...

随机推荐

  1. MFC 按钮如何改变颜色

    我们发现想改变对话框的背景颜色是很简单的,但是对话框的背景颜色改变了后,我们发现按钮的颜色没有改变,如下图. 这样做出来的对话框看起来,不是很自然,我们也想把按钮的颜色改变一下.这就用到了按钮的重绘. ...

  2. Linux Yum 安装JDK

    1.查看CentOS自带JDK是否已安装. 输入:yum list installed |grep java. 2.若有自带安装的JDK,如何卸载CentOS系统自带Java环境? 卸载JDK相关文件 ...

  3. python中list作为全局变量无需global声明的原因

    发现一个问题. python中list变量作为全局变量时,在函数中可以直接修改. 而普通变量则需要先在函数中global声明,否则会报错. 例如: a = 1 def fun(): global a ...

  4. Hadoop单机模式安装-(1)安装设置虚拟环境

    网络上关于如何单机模式安装Hadoop的文章很多,按照其步骤走下来多数都失败,按照其操作弯路走过了不少但终究还是把问题都解决了,所以顺便自己详细记录下完整的安装过程. 此篇主要介绍如何在Windows ...

  5. Android 登录界面与首页的设计

    全屏效果 //取消标题,取消状态栏 this.requestWindowFeature(Window.FEATURE_NO_TITLE); this.getWindow().setFlags(Wind ...

  6. 被Unity5坑惨了

    各种不明所以的crash,导致crash率从0.5%瞬间暴涨到10%. Unity5还是非常不稳定,慎入慎入...

  7. Windows - 性能监控之磁盘剩余空间大小警报

    开始 -> 运行 -> 键入命令 perfmon.msc 数据收集器(Data Collector Sets) -> 用户自定义(User Defined)

  8. jq 剪切板

    文章链接 http://www.cnblogs.com/lkxsnow/p/5372665.html http://www.w3cfuns.com/notes/17735/020c2e68a60342 ...

  9. 尽量用goto代替尾递归

    void PrintList(List L) { if(L!=Null) { PrintElement(L->Element); PrintLisr(L->Next); } } 所谓尾递归 ...

  10. java.lang.IllegalStateException: Web app root system property already set to different value

    webAppRootKey是在java web项目的web.xml配置文件中表示项目的唯一标示,在Eclipse调试Web项目时,项目的路径是一个临时路径,不在真正的路径下,可以通过log4j日志的方 ...