一、项目申明
本博客使用的所有素材(包括但不限于图片、视频、音频、文字等)已购买版权,仅供个人学习、交流、参考和研究使用,同学们如果想要用于个人项目或商业游戏请到https://itch.io/购买版权进行使用,否则需承担因使用该素材而产生的全部责任。本博客不承担任何直接或间接的法律责任。
二、效果展示
露娜
三、场景搭建
1.拖入地图素材

注意:
MapW的层次顺序为:0

MapH的层次顺序为:7 (为了能遮挡人物,所以图层顺序大一点)

2.拖入主角人物素材
(1)找到如图红色标注的素材,拖入Hierarchy视图中

(2)命名为Player,复制一下,让两个变为父子关系,同时删除父物体的Sprite Render组件,修改子物体的名字为为Texture

(3)调整Texture的位置,使得子物体的脚底位置恰好是父物体的轴心点位置,如图所示:

(4)Texture的图层顺序为2

(5)主角父物体的tag标签修改为 Player

3.拖入小狗素材
(1)将制定素材拖入场景中
(2)个性化放置到合适位置即可

(3)图层顺序为2

4.拖入马匹的素材
(1)将制定素材拖入场景中
(2)个性化放置到合适位置即可

(3)图层顺序为2

5.拖入商人素材
(1)将制定素材拖入场景中
(2)个性化放置到合适位置即可

(3)图层顺序为2

四、控制主角人物的移动
1.创建脚本文件并编辑代码
(1)在Project窗口的Asset下创建脚本文件夹Scripts
(2)在脚本文件夹下创建名为 PlayerControl的脚本

(3)挂载脚本到Player上,并给Player添加运动必备组件碰撞器Collider和刚体Rigidbody

(4)双击脚本,打开脚本,编辑代码
using System.Collections;
using System.Collections.Generic;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
static private string x = "Horizontal";
static private string y = "Vertical";
private float xValue = 0;
private float yValue = 0;
private Rigidbody2D PlayerRig;
public float moveSpeed = 100f;
private int maxHp = 100;
private int nowHp = 100;
public int MaxHp { get { return maxHp; } }
public int NowHp { get { return nowHp; } }
private void Start()
{
PlayerRig=GetComponent<Rigidbody2D>();
}
private void Update()
{
xValue = Input.GetAxis(x);
yValue = Input.GetAxis(y);
Vector2 newPosition = new Vector2(this.transform.position.x + xValue * moveSpeed*Time.deltaTime, this.transform.position.y + yValue * moveSpeed * Time.deltaTime);
PlayerRig.MovePosition(newPosition);
}
public void ChangeHP(int change)
{
nowHp = Mathf.Clamp(nowHp+change,0,maxHp);
Debug.Log(nowHp+"/"+maxHp);
}
}
2.修改Player相关组件的参数
(1)角色控制脚本的相关参数(个性化调整即可)

(2)碰撞器的大小设置,如图即可(个性化设置,差不多即可)

(3)刚体设置
取消重力和冻结Z轴旋转

3.测试运行(调整到适合速度即可)
WASD控制移动

五、预制体以及相应脚本的创建
1.地图物体——蜡烛预制体的创建
(1)将Candle素材拖入场景中

(2)修改图层顺序为2,修改大小为1.5倍

(3)将场景中的蜡烛再拖回Assets下的Prefabs文件夹,变成预制体

(4)在场景中创建空物体Candles,利用预制体创建五个蜡烛,并作为Candles的子物体进行统一管理(蜡烛位置随意放置,后期作为收集品可以收集)

2.地图物体——血瓶的创建以及血量变化脚本
(1)拖入血瓶素材并添加碰撞器,勾选触发器

(2)创建血瓶脚本,接触产生血量变化等
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HpBottle : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.tag=="Player")
{
PlayerControl playerControl = collision.GetComponent<PlayerControl>();
Debug.Log(playerControl.NowHp);
Debug.Log(playerControl.MaxHp);
if (playerControl.NowHp<playerControl.MaxHp)
{
playerControl.ChangeHP(10);
Destroy(gameObject);
}
}
}
}

六、实体化游戏世界地形
1.创建空物体并挂载多边形碰撞体
(1)创建一个父物体空物体MapColiders统一管理
(2)创建一个子物体空物体MapC1,并添加Polygon Colider 2D

2.根据地图绘制地形碰撞体

七、动画的设计与添加
1.主角动画的创建和动画生成树创建和设计
(1)选中相应的动画素材,拖入到Animation窗口中创建动画

(2)创建Animation和Animator文件夹用来分类管理动画

(3)在Texture上创建Animator组件,并挂在人物的动画控制器

(4)进入Animator窗口,右键创建生成树,进行分类命名(Idle,Walk等等)

(5)进入Idle生成树里面,修改为2D方向,添加八个方向,并把创建的相应动画添加到相应位置
(6)创建三个float参数,一个bool参数用来控制动画之间的过度条件

(7)Walk,Run等生成树动画如上进行操作


2.主角动画的过渡条件
(1)Idle——Walk

(2)Walk——Idle

(3)Walk——Run

(4)Run——Walk

(5)生成树Idle设计

(6)生成树Run,Walk同(5)
3.主角动画代码控制设计
(1)需求分析
- 方向一致性:待机时保持最后移动方向(如向左走后停止,待机动画仍朝左),而非默认朝一个方向;
- 状态区分:支持 “待机(8 方向)→ 步行(8 方向)→ 跑步(8 方向)” 三状态切换,且跑步需通过 Shift 键触发;
- 参数联动:动画状态与角色移动逻辑强关联(如停止移动时立即切换待机,按下 Shift 时同步切换跑步动画)。
(2)核心变量设计
| 变量类型 | 变量名 | 作用说明 | 关联 Animator 参数 |
|---|---|---|---|
| Animator | animator | 引用主角的 Animator 组件,用于传递动画参数 | - |
| Vector2 | LookDirection | 记录主角朝向(归一化向量),控制 8 方向动画 | LookX、LookY(float 类型) |
| Vector2 | newPosition | 记录当前输入的移动向量,判断是否处于移动状态 | MoveValue(float 类型) |
| bool | isRunning | 标记是否处于跑步状态(Shift 键触发) | IsRunning(bool 类型) |
| float | moveSpeed/runSpeed | 步行 / 跑步速度,控制移动逻辑与动画节奏匹配 | - |
(3)关键逻辑实现
【1】初始化模块(Start ())
private void Start()
{
// 绑定移动组件(Rigidbody2D)
PlayerRig = GetComponent<Rigidbody2D>();
// 绑定动画组件(Animator),支持子物体查找
animator = GetComponentInChildren<Animator>();
}
【2】输入检测与参数传递模块(Update ())
通过 Unity 的Input.GetAxis()获取键盘或手柄的移动输入,xValue对应左右方向,yValue对应上下方向(值范围:-1~1)
读取移动输入:获取 Horizontal/Vertical 轴值
xValue = Input.GetAxis(x); // 等价于Input.GetAxis("Horizontal")
yValue = Input.GetAxis(y); // 等价于Input.GetAxis("Vertical")
只有当输入值超过阈值(0.1f,过滤微小误触)时,才更新LookDirection(归一化后的朝向向量)。停止移动后,LookDirection保持最后一次的朝向,从而实现 “8 方向待机”。
更新朝向:确保待机时方向不丢失
if (Mathf.Abs(xValue) > 0.1f || Mathf.Abs(yValue) > 0.1f)
{
// 归一化:确保斜向移动速度与横向/纵向一致(避免斜向跑得更快)
LookDirection = new Vector2(xValue, yValue).normalized;
}
通过Input.GetKey()检测左右 Shift 键,标记isRunning状态。只有在有移动输入时(newPosition.magnitude > 0.1f),跑步状态才生效,避免 “站立时按 Shift 触发跑步动画” 的逻辑错误。
检测跑步状态:Shift 键触发
// 检测Shift键输入(支持左右Shift)
isRunning = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
// 记录当前移动向量(未归一化,用于判断是否处于移动状态)
newPosition = new Vector2(xValue, yValue);
(4)传递参数到 Animator:驱动动画状态切换
通过animator.SetXXX()方法传递到 Animator 窗口,直接控制动画状态:
MoveValue:传递移动向量的长度(0~√2),判断 “待机”(0)或 “移动”(>0);LookX/LookY:传递朝向向量的 x/y 分量(-1~1),控制 8 方向动画(如向左时 LookX=-1,向上时 LookY=1);IsRunning:传递跑步状态(true/false),控制 “步行” 与 “跑步” 动画切换。
animator.SetFloat("MoveValue", newPosition.magnitude);
animator.SetFloat("LookX", LookDirection.x);
animator.SetFloat("LookY", LookDirection.y);
// 只有移动时,跑步状态才生效
animator.SetBool("IsRunning", isRunning && newPosition.magnitude > 0.1f);
(5)移动逻辑模块(FixedUpdate ())
private void FixedUpdate()
{
// 根据跑步状态选择当前速度
float currentSpeed = isRunning ? runSpeed : moveSpeed;
// 控制Rigidbody2D移动(与动画状态同步)
PlayerRig.velocity = newPosition * currentSpeed * Time.deltaTime / Time.fixedDeltaTime;
}
(6)完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
static private string x = "Horizontal";
static private string y = "Vertical";
private float xValue = 0;
private float yValue = 0;
private Rigidbody2D PlayerRig;
private Animator animator;
public float moveSpeed = 3f; // 步行速度
public float runSpeed = 5f; // 跑步速度
private int maxHp = 100;
private int nowHp = 100;
private bool isRunning = false; // 跑步状态标记
public int MaxHp { get { return maxHp; } }
public int NowHp { get { return nowHp; } }
public Vector2 LookDirection = Vector2.zero;
public Vector2 newPosition = Vector2.zero;
private void Start()
{
PlayerRig = GetComponent<Rigidbody2D>();
animator = GetComponentInChildren<Animator>();
}
private void Update()
{
xValue = Input.GetAxis(x);
yValue = Input.GetAxis(y);
// 检测移动输入并更新朝向
if (Mathf.Abs(xValue) > 0.1f || Mathf.Abs(yValue) > 0.1f)
{
LookDirection = new Vector2(xValue, yValue).normalized;
}
// 检测Shift键输入判断是否跑步
isRunning = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
newPosition = new Vector2(xValue, yValue);
// 向动画系统传递参数
animator.SetFloat("MoveValue", newPosition.magnitude);
animator.SetFloat("LookX", LookDirection.x);
animator.SetFloat("LookY", LookDirection.y);
animator.SetBool("IsRunning", isRunning && newPosition.magnitude > 0.1f); // 只有移动时才可能跑步
}
private void FixedUpdate()
{
// 根据是否跑步选择不同的速度
float currentSpeed = isRunning ? runSpeed : moveSpeed;
PlayerRig.velocity = newPosition * currentSpeed * Time.deltaTime / Time.fixedDeltaTime;
}
public void ChangeHP(int change)
{
nowHp = Mathf.Clamp(nowHp + change, 0, maxHp);
Debug.Log(nowHp + "/" + maxHp);
}
}
4.怪兽动画的创建和动画生成树的创建和设计
步骤和操作同主角,不过多赘述

5.怪兽动画的过渡条件

6.怪兽动画代码控制设计
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyControl : MonoBehaviour
{
public bool vertical;
public float speed = 5;
public float changeTime = 5;
private int direction = -1;
private float timer;
private Rigidbody2D rigidbody2d;
private Animator animator;
private void Start()
{
rigidbody2d = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
timer=changeTime;
}
private void Update()
{
timer-=Time.deltaTime;
if(timer<=0)
{
direction=-direction;
timer=changeTime ;
}
}
private void FixedUpdate()
{
Vector3 pos=rigidbody2d.position;
if(vertical)
{
animator.SetFloat("LookX", 0);
animator.SetFloat("LookY", direction);
pos.y = pos.y+speed*direction*Time.fixedDeltaTime;
}
else
{
animator.SetFloat("LookX", direction);
animator.SetFloat("LookY", 0);
pos.x = pos.x + speed * direction * Time.fixedDeltaTime;
}
rigidbody2d.MovePosition(pos);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.CompareTag("Player"))
{
GameManager.Instance.EnterOrExitBattle(true);
}
}
}
八、摄像机镜头跟随
1.导入插件Cinemachine

2.创建虚拟摄像机并设置参数
(1)创建虚拟摄像机

(2)设置跟随对象和视角大小

(3)添加边界组件,设置地图边界(避免视角包含地图外的场景)


九、攀爬区域的设计
1.攀爬动画的设计
参考第七章的人物动画设计与添加
2.攀爬区域的绘制
(1)创建空物体,添加多边形碰撞器,勾选触发器,进行攀爬区域的绘制

(2)创建一个父物体统一管理攀爬区域

3.攀爬区域的代码设计
(1)修改玩家控制代码,确保玩家触发区域可以执行相应的函数
......
public void SetClimb(bool IsClimb)
{
animator.SetBool("IsClimb", IsClimb);
}
......
(2)创建攀爬区域的脚本代码——ClimbArea
玩家触发和离开区域执行相应的代码逻辑
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ClimbArea : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.tag=="Player")
{
collision.GetComponent<PlayerControl>().SetClimb(true);
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.tag == "Player")
{
collision.GetComponent<PlayerControl>().SetClimb(false);
}
}
}
(4)将脚本挂载到攀爬区域物体上即可
十、UI设计
1.头像UI设计
(1)创建画布,画布UI模式设置为大小根据屏幕大小变化

(2)创建一个image用来存放头像,锚点设置为右上角

(3)再创建一个Image用来存放头像外边框,锚点设置为如图

2.血条和蓝条设计
(1)创建image_Hp,拖入血条背景
(2)创建image_Red,拖入血条素材,改变颜色
(3)创建image_Red的空父物体,添加image和mask组件,拖入mask素材

(4)复制一下,修改颜色为蓝色,即为蓝条
3.血条和蓝条的代码设计
(1)核心变量设计
- 单例模式(Instance):通过
Awake()方法初始化单例,确保全局只有一个UIManager实例,方便其他脚本(如角色控制器)随时调用血条更新方法。 - UI 元素引用(HpImage、MpImage):通过
public Image类型变量关联场景中的血条和蓝条图片组件,这两个Image通常在 Unity 编辑器中分别赋值为 “血条填充图” 和 “蓝条填充图”。
(2)血条更新逻辑
根据当前血量 / 蓝量与最大值的比例,动态调整填充图片的宽度。
// 调整血条填充图的水平宽度
HpImage.rectTransform.SetSizeWithCurrentAnchors(
RectTransform.Axis.Horizontal, // 调整水平方向尺寸
HpOrMax * HpImage.rectTransform.rect.width // 计算新宽度:比例 × 原始宽度
);
(3)完整的代码 UIManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
private UIManager Instance;
public Image HpImage;
public Image MpImage;
private void Awake()
{
Instance = this;
}
public void SetHpValue(float HpOrMax)//设置血条
{
HpImage.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, HpOrMax * HpImage.rectTransform.rect.width);
}
public void SetMpValue(float HpOrMax)//设置蓝条
{
HpImage.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, HpOrMax * HpImage.rectTransform.rect.width);
}
}
十一、战斗场景的制作
1.游戏管理器以及脚本的制作
(1)创建空物体GameManager
(2)创建脚本GameManager并挂在到该空物体上
(3)修改PlayerControl代码,把上面的游戏场景相关代码迁移到GameManager脚本中
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
private int maxHp = 100;
private int nowHp = 50;
public int MaxHp { get { return maxHp; } }
public int NowHp { get { return nowHp; } }
private void Awake()
{
Instance = this;
}
public void ChangeHP(int change)
{
nowHp = Mathf.Clamp(nowHp + change, 0, maxHp);
Debug.Log(nowHp + "/" + maxHp);
}
}
2.游戏场景的制作
(1)将战斗场景素材拖拽大场景中作为摄像头的子物体,调整大小
(2)将战斗的角色和怪兽拖入到场景素材下作为子物体

3.战斗玩家以及动画创建和设计
(1)拖入战斗玩家素材
(2)根据素材创建动画并分类

(3)创建动画树和过渡条件

4.战斗怪兽以及动画创建和设计
(1)拖入战斗玩家素材
(2)根据素材创建动画并分类

(3)创建动画

5.对战UI交互制作
(1)创建1个Image,2个Text,5个Button

十二、跳跃区域的设计
1.跳跃动画的设计
参考第七章的人物动画设计与添加

2.跳跃区域的绘制
(1)创建一个空物体,绘制碰撞体合适大小,并创建跳跃区域脚本,并挂载上面,如图
(2)创建两个跳跃点,分别在两边

(3)多创建几个跳跃区域散布在地图需要跳跃的位置上即可
(4)安装DOTween插件

3.跳跃区域的代码设计
(1)源代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class JumpArea : MonoBehaviour
{
public Transform JumpA;
public Transform JumpB;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Player")
{
collision.GetComponent<PlayerControl>().SetJump(true);
float disA = Vector3.Distance(collision.GetComponent<PlayerControl>().transform.position, JumpA.position);
float disB = Vector3.Distance(collision.GetComponent<PlayerControl>().transform.position, JumpB.position);
Transform GoPosition=disA > disB ? JumpA : JumpB;
collision.GetComponent<PlayerControl>().transform.DOMove(GoPosition.position, 0.5f).SetEase(Ease.Linear).OnComplete(() => { EndJump(collision.GetComponent<PlayerControl>()); });
Transform playerTrans = collision.GetComponent<PlayerControl>().transform.GetChild(0);
Sequence sequence = DOTween.Sequence();
sequence.Append(playerTrans.DOLocalMoveY(1.5f,0.25f).SetEase(Ease.InOutSine));
sequence.Append(playerTrans.DOLocalMoveY(0.61f, 0.25f).SetEase(Ease.InOutSine));
sequence.Play();
}
}
public void EndJump(PlayerControl playerControl)
{
playerControl.SetJump(false);
}
}
(2)核心逻辑与方法设计
-
碰撞检测触发(
OnTriggerEnter2D方法)- 触发条件:当 2D 碰撞体(
Collider2D)进入当前物体的碰撞范围时调用(需确保当前物体和玩家物体的碰撞体设置为 “触发器”,且有 2D 刚体组件)。 - 标签判断:通过
collision.tag == "Player"筛选碰撞对象,仅对玩家生效,避免其他物体触发跳跃逻辑。
- 触发条件:当 2D 碰撞体(
-
跳跃状态控制
- 调用玩家控制器(
PlayerControl)的SetJump(true)方法:在跳跃开始时设置玩家为 “跳跃中” 状态(推测用于禁用玩家自主移动、防止重复触发跳跃等)。 - 跳跃结束后通过
EndJump方法调用SetJump(false):恢复玩家正常状态。
- 调用玩家控制器(
-
跳跃位移实现
- 使用 DOTween 插件的
DOMove方法:实现玩家位置从当前点到GoPosition的平滑移动,设置移动时间为 0.5 秒,动画曲线为Ease.Linear(线性移动,保证速度均匀)。 - 回调函数
OnComplete:移动结束后自动调用EndJump方法,完成状态重置。
- 使用 DOTween 插件的
-
跳跃动画效果
- 获取玩家子对象(
playerTrans = transform.GetChild(0)):通常为玩家的视觉表现模型(如角色 sprite),用于单独控制跳跃时的上下浮动动画。 - 序列动画(
Sequence):通过 DOTween 的序列动画实现 “上升 - 下降” 的跳跃弧度:- 先执行
DOLocalMoveY(1.5f, 0.25f):0.25 秒内沿 Y 轴(本地坐标)上升到 1.5 位置,动画曲线为Ease.InOutSine(平滑正弦曲线,模拟自然起跳)。 - 再执行
DOLocalMoveY(0.61f, 0.25f):0.25 秒内下降到 0.61 位置(恢复接近初始高度),曲线同上,模拟落地效果。
- 先执行
- 获取玩家子对象(
十三、后续自行拓展实现,本文起到抛砖引玉的作用
十四、完整功能项目以及源代码
通过网盘分享的文件:Luna's Fantasy
链接: https://pan.baidu.com/s/1k1PTa-GXS0NI7jF7hP8wWw?pwd=1111 提取码: 1111
--来自百度网盘超级会员v5的分享
2597

被折叠的 条评论
为什么被折叠?



