Unity 2D游戏开发案例学习——Robble Swifthand(上)

1 概述

该学习案例来自b站up主M_Studio的系列视频从独立游戏学习开发,此篇博客为案例学习笔记
案例素材来自上架独立游戏***Robble Swifthand***,本案例中将通过此独立游戏学习游戏开发的知识

推荐Unity版本:2018.3以上

2 素材导入整理 Import Package

资源导入

资源素材提供自M_Studio,推荐前往观看视频
通过Import Package中的Custom Package(个人资源包)来进行本地完整程序包的导入

资源概览

在这里插入图片描述
1._Extended 拓展
Props 一些游戏后期可用的预制体资源
2.Addons 插件
TextMash Pro —— Ui字幕插件
Tilemap 2D —— Extra拓展包,可以让Tilemap的功能得到有力的扩充
3.Audio 声音资源
4.Fonts 字体资源
5.Gizmos 小插件
Cinemachine插件 —— 2D摄像机跟踪
6.Level 关卡资源
Sprites —— 精灵图片资源
Materials —— 材质资源
7.Props 预制资源
8.Robbie 游戏主角相关资源
9.Scene 场景
10.Scripts 脚本(不包含已经完成的脚本)
11.Tilemap 瓦片地图资源
12.UI UI元素资源
13.VFX 视觉特效资源

创建Tilemap

从Level文件夹下的Sprites文件夹中,将我们所需的三个切割好的Tilemap资源分别导入Palette中,生成我们所需要的画布(Background,Shadow,Platforms)
在这里插入图片描述
在已经创建好的画布中,为了方便之后的绘制,(在Edit模式下)可以通过select操作来调整已经生成的瓦片的位置,也可以再导入单个分割好的素材到画布中填补一些削去重复素材而导致的缺口

在这之后,我们就可以在场景中创建Tilemap对象来绘制相应的场景了。一个Grid下可以创建多个Tilemap来分类承载不同的素材(在绘制时在Active Tilemap中选择来激活需要的Tilemap),也可以利用Sorting Layer属性实现场景的遮罩

3 Tilemap(Rule Tile)

前置准备

1.创建其他Tilemap并添加相应的Sorting Layer
在这里插入图片描述
2.添加玩家角色
在Robbie文件夹中找到游戏主角“Robbie”的Sprite,将其拖入场景中,大致地比较其与Tilemap的相对大小,方便场景的绘制

绘制场景

关于 Background 和 Background Details(分层绘制

在Grid中,我们将背景的Tilemap分成了两个部分: Background 和 Background Details ,其中,Background Details 在Sorting Layer的更上层
这么设置的原因是,画布中同属于Background的Tile,如果选择同一个Tilemap进行绘制,可能会产生冲突,比如下面的两个素材:
在这里插入图片描述
“鬼脸”这一雕塑本身应当处于墙壁外侧,依附于墙壁,但在绘制于同一Tilemap时,素材本身会覆盖掉原先的墙壁,使其自带的空白部分显露出来,这并不符合我们的本意。因此我们通过将他们绘制在不同的Tilemap中来解决这样的冲突:
在这里插入图片描述

Rule Tile

在项目中,由于我们导入了 Extra拓展包 ,我们的菜单栏得到了相应的扩展来方便的使用其中的功能:
在这里插入图片描述
我们选择Rule Tile —— 设立Tile的组成规则来快速绘制场景
1.首先,我们在Tilemap的Background文件夹下创建一个Rule Tile,可以看到其大概的属性:
在这里插入图片描述
2.我们为其设定默认的精灵图片——这代表了在没有相应规则匹配的情况下会被绘制出来的Tile
随后,我们将文件夹中创建的Rule Tile导入Palette中,可以看到,它所呈现的样式与我们设定的默认Sprite一致
在这里插入图片描述
如果这时在场景中进行绘制,就会得到和通常绘制一样的图形组

3.我们在Tiling Rules中添加新的条目——这意味着我们设立了某种生成规则,其具体规则在条目中的“九宫格”部分进行设立:
在这里插入图片描述
场景中先前绘制的图形也随着规则的设立而自动的更改:
在这里插入图片描述

借助Rule Tile这样的特性,我们可以快速的获得符合需求的场景
此外,需要注意的有:
①素材的复用,
②以及同一种Tile可能因为在不止一种情况下使用而需要创立多种规则
③Tiling Rules下制定的规则具有执行顺序,会优先考虑上面的规则,因此部分规则需要调整其优先级
根据需要,我们对这一类Tile创立了如下的规则:
在这里插入图片描述
有了如上的规则后,我们就可以绘制这一Tile的场景了

4.Shadow的绘制规则:
在这里插入图片描述

Random Rule Tile

Platforms画布中的Tile元素并没有上面两种Tile那样相应的规则,因此在这里我们使用Rule Tile中的随机瓦片
我们将Output属性修改为Random,设置其随机空间大小为4,并将需要的四种sprite资源赋值进去:
在这里插入图片描述
这样,我们就可以在绘制时便捷地形成随机的Platforms了:
在这里插入图片描述
(推荐使用Palette中的矩形填充)

补充

Platforms和Shadow的组合效果:
在这里插入图片描述

4 物理组件 Rigbody&Collider

为地形添加物理组件

为了实现角色与场景地形的交互,我们需要为相应的Tilemap添加物理组件,相关设置如下:
在这里插入图片描述

为玩家角色添加物理组件

我们同样需要为玩家角色添加相应的物理组件来实现交互效果,具体设置如下:
在这里插入图片描述
其中,Interpolate属性表示物体运动的插值模式,在此模式下物理引擎会在物体的运动帧之间进行插值,使得运动更加自然
另外,注意锁定人物z轴的旋转,防止人物不自然的旋转动作

除此以外,为了避免人物跑动撞到墙体后因为速度等原因产生的异常交互,我们可以为人物的Collider添加物理材质,使之能够从墙体上滑落下来

设置Layer

为场景中的物体添加相应的Layer,方便在之后的代码中编写交互逻辑时的检测:
在这里插入图片描述

5 地面移动代码

在这一案例中,玩家控制角色在地面上移动时具有常规移动以及下蹲移动两种状态。为了实现这两个状态的基本功能,我们需要为角色编写相关的代码,基本的编程实现如下(后续还会进行补充,诸如更完善的检测机制以及动画的切换等):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
	//player对象组件引用
	private Rigidbody2D player_rigbody;
	private BoxCollider2D player_collider;

	[Header("移动参数")]
	public float speed = 8f;
	public float crouchSpeedDivisor = 3f;						//下蹲状态移动速度缩小幅度

	float xVelocity;											//水平移动速度

	[Header("角色状态")]
	public bool isCrouch;

	//碰撞体参数
	Vector2 stand_collider_size;
	Vector2 stand_collider_offest;
	Vector2 crouch_collider_size;
	Vector2 crouch_collider_offest;


	private void Start()
	{
		player_rigbody = GetComponent<Rigidbody2D>();
		player_collider = GetComponent<BoxCollider2D>();
		//获取站立状态角色碰撞体参数并对下蹲状态参数赋值(注:如果引用的是Collider而不是具体的BoxCollider,无法同下面一样的获取参数)
		stand_collider_size = player_collider.size;
		stand_collider_offest = player_collider.offset;
		crouch_collider_size = new Vector2(player_collider.size.x, player_collider.size.y / 2);
		crouch_collider_offest = new Vector2(player_collider.offset.x, player_collider.offset.y / 2);
	}

	private void Update()
	{
		
	}

	private void FixedUpdate()
	{
		GroundMovement();
	}

	void GroundMovement()//地面移动
	{
		if (Input.GetButton("Crouch"))
			Crouch();
		else if (!Input.GetButton("Crouch") && isCrouch)
			StandUp();

		xVelocity = Input.GetAxis("Horizontal");

		if (isCrouch)
			xVelocity /= crouchSpeedDivisor;

		player_rigbody.velocity = new Vector2(xVelocity * speed, player_rigbody.velocity.y);

		FlipDirection();
	}

	void FlipDirection()//左右翻转
	{
		if (xVelocity < 0)
			transform.localScale = new Vector2(-1, 1);
		else if (xVelocity > 0)
			transform.localScale = new Vector2(1, 1);
	}

	void Crouch()//下蹲
	{
		isCrouch = true;

		//调整碰撞体大小
		player_collider.size = crouch_collider_size;
		player_collider.offset = crouch_collider_offest;
	}

	void StandUp()//站起(从下蹲中)
	{
		isCrouch = false;

		//恢复碰撞体大小
		player_collider.size = stand_collider_size;
		player_collider.offset = stand_collider_offest;
	}
}

此外,我们还需要为下蹲功能设置按键,对应在代码中的按键检测
在这里插入图片描述
完成了如上内容后,游戏中的人物已经可以进行简单的左右移动,蹲起切换以及使用下蹲移动通过障碍物了

值得注意的内容:具体碰撞体的引用和碰撞体参数的修改

6 跳跃代码(长按有加成)

在本节中,我们将编写角色跳跃功能有关的代码。我们游戏的角色可以在摁下按键时实现一次跳跃,若长摁按键可以获得一点点额外的跳跃高度,也可以配合上一节中的下蹲功能,下蹲时摁下跳跃可以跳到更高的高度,下面我们来实现这些功能
1.修改 Project Setting 中的重力数值从-9.81到-50(根据游戏制作团队的建议),以优化跳跃效果和手感
2.编写跳跃有关代码(在上面的代码中进一步完善):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
	//player对象组件引用
	private Rigidbody2D player_rigbody;
	private BoxCollider2D player_collider;

	[Header("移动参数")]
	public float speed = 8f;
	public float crouchSpeedDivisor = 3f;						//下蹲状态移动速度缩小幅度

	float xVelocity;                                            //水平移动速度

	[Header("跳跃参数")]
	public float jumpForce = 6.3f;
	public float jumpHoldForce = 1.9f;                          //长摁跳跃时的额外加成
	public float jumpHoldDuration = 0.1f;                       //长摁跳跃按键的有效加成时间
	public float jumpBoostByCrouch = 2.5f;                      //下蹲对跳跃的额外加成

	float jumpTime;												//跳跃时间

	[Header("角色状态")]
	public bool isCrouch;
	public bool isGround;
	public bool isJump;

	[Header("环境监测")]
	public LayerMask groundLayer;

	//碰撞体参数
	Vector2 stand_collider_size;
	Vector2 stand_collider_offest;
	Vector2 crouch_collider_size;
	Vector2 crouch_collider_offest;

	//按键设置
	bool jumpPressed;
	bool jumpHeld;
	bool crouchHeld;

	private void Start()
	{
		player_rigbody = GetComponent<Rigidbody2D>();
		player_collider = GetComponent<BoxCollider2D>();
		//获取站立状态角色碰撞体参数并对下蹲状态参数赋值(注:如果引用的是Collider而不是具体的BoxCollider,无法同下面一样的获取参数)
		stand_collider_size = player_collider.size;
		stand_collider_offest = player_collider.offset;
		crouch_collider_size = new Vector2(player_collider.size.x, player_collider.size.y / 2);
		crouch_collider_offest = new Vector2(player_collider.offset.x, player_collider.offset.y / 2);
	}

	private void Update()
	{
		//获取按键输入
		jumpPressed = Input.GetButtonDown("Jump");
		jumpHeld = Input.GetButton("Jump");
		crouchHeld = Input.GetButton("Crouch");
	}

	private void FixedUpdate()
	{
		PhysicsCheck();
		GroundMovement();
		MidAirMovement();
	}

	void PhysicsCheck()//物理环境判断
	{
		if (player_collider.IsTouchingLayers(groundLayer))
			isGround = true;
		else isGround = false;
	}

	void GroundMovement()//地面移动
	{
		if (crouchHeld && !isCrouch && isGround)
			Crouch();
		else if (!crouchHeld && isCrouch)
			StandUp();
		else if (!isGround && isCrouch)//从下蹲转换到跳跃状态时也需要恢复碰撞体
			StandUp();

		xVelocity = Input.GetAxis("Horizontal");

		if (isCrouch)
			xVelocity /= crouchSpeedDivisor;

		player_rigbody.velocity = new Vector2(xVelocity * speed, player_rigbody.velocity.y);

		FlipDirection();
	}

	void MidAirMovement()
	{
		if (jumpPressed && isGround && !isJump)//检测到地面且没有处于跳跃状态然后按下跳跃键
		{
			if (isCrouch && isGround)
			{
				StandUp();

				player_rigbody.AddForce(new Vector2(0f, jumpBoostByCrouch), ForceMode2D.Impulse);//施加下蹲起跳带来的额外跳跃力
			}

			isGround = false;
			isJump = true;

			jumpTime = Time.time + jumpHoldDuration;//开始跳跃计时

			player_rigbody.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);//施加正常跳跃力
			//其中, ForceMode2D.Impulse 适用于瞬时施加的力
		} else if (isJump)//处于跳跃状态
		{
			if (jumpHeld)//跳跃状态中保持摁下跳跃按键
				player_rigbody.AddForce(new Vector2(0f, jumpHoldForce), ForceMode2D.Impulse);//施加长摁跳跃带来的额外跳跃力

			if (jumpTime < Time.time)//跳跃总时间超过Duration
				isJump = false;
		}
	}

	void FlipDirection()//左右翻转
	{
		if (xVelocity < 0)
			transform.localScale = new Vector2(-1, 1);
		else if (xVelocity > 0)
			transform.localScale = new Vector2(1, 1);
	}

	void Crouch()//下蹲
	{
		isCrouch = true;

		//调整碰撞体大小
		player_collider.size = crouch_collider_size;
		player_collider.offset = crouch_collider_offest;
	}

	void StandUp()//站起(从下蹲中)
	{
		isCrouch = false;

		//恢复碰撞体大小
		player_collider.size = stand_collider_size;
		player_collider.offset = stand_collider_offest;
	}
}
一些问题:

在更新了代码逻辑后,角色使用下蹲功能时,由于碰撞体大小和偏移数值问题导致改变后的碰撞体并没有触及地面,因此会很快的复原,长摁下蹲按键会反复这一过程

7 如何用射线判断碰撞体 Raycast

在上面的功能中,判断角色碰撞体与环境交只是简单地用碰撞体本身以及IsTouchingLayers函数直接进行碰撞检测,但这并不是很好的解决方案——项目中的墙壁和地面处于相同的Layer,角色与墙壁的交互会因此显得不正常,而且也可能导致上一节中的问题。因此我们考虑采用射线检测的方式来完善检测逻辑

添加新的环境监测变量

在这里插入图片描述
补充释义:footOffest为人物中心到两脚的距离(项目中,角色贴图的中心点设置在了脚底中间,因此使用表示脚底的相对位置时y方向值为0)

在PhysicsCheck中添加射线检测

我们在之前创建的物理检测函数中借助新添设的变量的数据来实现射线检测:
在这里插入图片描述
完成射线创建以显示的代码并修改地面检测条件后,再次启动游戏可以看到我们绘制的射线(用于指代实际的射线),并且角色以及可以正常的下蹲(碰撞体不会因为偏移而不断试图恢复原状)
在这里插入图片描述

通过方法重载来更简洁的使用射线检测

除了上面的检测逻辑以外,我们还需要其他一系列的射线检测。如果都按照上述代码编写,将会十分繁琐臃肿,因此我们采用方法重载来简洁方便的实现我们设想的功能
在这里插入图片描述
根据我们的需要完成了上面的方法重载后,回到PhysicsCheck方法中完成左右脚对地面的射线检测:
在这里插入图片描述
(也可以在重载的方法中添加代码,使射线检测成功时有不同的颜色)
在这里插入图片描述
完成上面的设置后,游戏中实际的效果如下:
在这里插入图片描述

加入头部的射线检测

添加新的角色状态isHeadBlocked(头部存在阻塞)
在这里插入图片描述
在PhysicsCheck方法中新增头部射线检测部分
在这里插入图片描述
将isHeadBlocked状态作为判断条件添加到角色移动控制代码中
在这里插入图片描述
完成后游戏中的效果如下:
在这里插入图片描述

补充:

1.将重载的Raycast方法中绘制射线的方向和长度合并修正为rayDirection*length(单位向量乘以长度系数)
在这里插入图片描述

8 悬挂(isHanging)

在这一节中,我们将实现角色的悬挂功能
在这里插入图片描述
如官方图例所示,设置悬挂功能所需要的射线检测时,我们需要判断角色头顶附近没有其他额外的碰撞体阻挡且头顶前端的下方存在有可悬挂的碰撞体,还需要判断角色离墙面的距离。需要注意的是,在上面的2、3中,起点也被覆盖的射线无法正常的检测碰撞体,因此显示为绿色。下面我们在代码中编写对应的检测逻辑

悬挂的射线检测

添加新的环境监测变量,用于悬挂相关的射线检测
在这里插入图片描述
更新角色状态布尔变量isHanging,用于标识悬挂状态
在这里插入图片描述
借助上面的变量在PhysicsCheck中编写悬挂所需要的三种射线检测代码和状态切换代码
在这里插入图片描述

悬挂的实现

我们借助修改Rigbody的 Body Type 属性到static来实现角色的悬挂静止功能。因此在代码中添加如下的代码
在这里插入图片描述
但仅仅做了如上的修改后,角色的悬挂仍然存在问题——处于悬挂状态的角色可以随意切换左右方向,角色悬挂静止的位置可能会不自然的抬高(由于向下的抓取检测射线只要检测角色即静止),因此我们还需要进一步调整
在悬挂状态控制代码中调整角色到合适的位置:
在这里插入图片描述
在移动控制代码中添加悬挂判断条件以阻断悬挂时移动:
在这里插入图片描述

悬挂的解除(悬挂时动作)

我们希望角色在悬挂状态时,如果摁下跳跃就可以成功跳到平台上,摁下就可以松手下落到地面

我们在代码跳跃参数部分添加一个新的变量,表示悬挂时摁下跳跃键应当对角色施加的跳跃力
在这里插入图片描述
随后在跳跃控制代码中添加悬挂时的两个动作处理逻辑
其中,变量crouchPressed是新添加的按键判断指代变量,用于判断Crouch的单次摁下(如果使用crouchHeld,由于这一变量通过GetButton获得,在操纵角色下蹲跳跃时可能不能及时松开下蹲按键,导致角色一悬挂即掉落)
在这里插入图片描述

补充:

1.角色下蹲状态时使用跳跃会恢复碰撞体并卡出地形,需要在下蹲跳跃代码部分补充头部射线检测来避免这一问题
在这里插入图片描述
2.由于暂时不明的原因角色无法正确进入悬挂状态
次日更新:
在这里插入图片描述
该if语句块不需要else,在此更正

9 摄像机(透视 | 跟随) Cinemachine

在这一节中,我们将调整摄像机,使镜头可以跟随角色,并添加透视的效果

调整景深和拍摄模式

在Camera的Projection选择Orthography模式进行拍摄时,在3D视角下可以看到其拍摄效果
在这里插入图片描述
上面这种模式不利于我们场景效果的体现,因此我们将Projection调整为Perspective,并调整Grid中每一层场景的z轴位置,来增加场景的透视效果
在这里插入图片描述

使用Cinemachine实现镜头控制

通过安装Cinemachine插件可以让我们便捷地实现镜头控制
(在 Window -> PackageManager 中 install )
完成安装后,在项目的菜单栏中可以看到Cinemachine并选择要使用的功能
在这里插入图片描述
我们选择创建2D相机,然后来调整其属性设置

设置Camera边界

我们在 Cinemachine 下新增一个组件 Cinemachine Confiner ,用于实现Camera移动时的边界控制(避免拍摄到场景边界使游戏氛围有所破坏)
在这里插入图片描述
如图所示,我们需要为这一组件添加一个碰撞体来完成镜头的限制,因此我们在场景中创建一个新的空物体 Camera Bounds 用于承载我们所需要的这个碰撞体
在这里插入图片描述
我们选择 Polygon Collider 2D (多边形碰撞体)来控制镜头的边界,利用其边界可以自由调控的特性,我们可以在场景的不同位置有不同的镜头限制,便于比如隐藏区域的探索等
将碰撞体设置为 Is Trigger 模式并将空物体拖入 Cinemachine Confiner 中,我们的镜头限制就完成了。Camera可以在指定的范围内跟随角色移动
在这里插入图片描述

补充:

1.更正代码失误
在这里插入图片描述
(判断条件 isGround 和上面的语句存在冗余,可以删去只保留最外层的判断)

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值