分享一个用Unity制作的小游戏 暗影随行

2016年09月26日 17:58 0 点赞 0 评论 更新于 2025-11-21 20:30
分享一个用Unity制作的小游戏 暗影随行

最近玩了一款名为“暗影惊吓”的小游戏,虽玩法简单,但趣味性十足。本文将介绍如何使用Unity实现一个类似的游戏。

项目源码

项目源码名称为:DarkFollow

主要工作分析

  1. 主角的控制(重点):包含左右移动、跳跃以及动画播放等功能。
  2. 场景的设计:涵盖地板、空中平台和背景等元素。
  3. 影子跟随(重点):游戏中会有有害影子跟随主角。
  4. 奖励:玩家获得奖励可加分,部分奖励还会触发影子的产生。
  5. 震动:主角落地时,界面会根据降落高度产生震动效果。

主角控制

左右移动

通过键盘输入为刚体设置速度,同时利用Speed变量控制动画中IdleRun状态的切换。以下是相关代码:

// 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);
}

跳跃

首先要确定玩家是否站在地板上,可在角色底部添加“Foot”。参考链接:http://www.taikr.com 然后以“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);
}

动画播放

动画状态机的设计需根据游戏需求进行合理规划。

边界穿越

玩家可以从左边界走到右边界,反之亦然。为左右边界添加碰撞框,当玩家碰到碰撞框时,将其位置设置到另一边。代码如下:

// 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);
}
}

影子跟随

记录影子数据

为了生成影子,需要记录玩家的历史数据,包括位置、面向和动画状态机的切换变量。定义如下数据结构:

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);
}

黑色影子

创建一个黑色的Material,并将其赋值给SpriteRenderer,即可得到黑色影子。

会跟随的影子

将玩家的历史数据赋值给影子,但要注意历史数据使用List存储,越后面的数据越新。新生成的影子在旧影子后面,应从后往前读取数据,让旧影子读取更新的数据以靠近玩家,且影子和玩家需保持一定距离。代码如下:

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);
}

管理影子

游戏中可能会生成多个影子,需要对其进行管理,保证影子之间有一定距离。代码如下:

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);
}

其他

奖励

设置所有奖励的生成点,然后随机生成奖励。代码如下:

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());
}

同时,需根据设置生成不同类型的奖励。

摄像机抖动

根据玩家的跳跃高度决定抖动幅度,使用协程实现抖动效果。代码如下:

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);
}

作者信息

孟子菇凉

孟子菇凉

共发布了 3994 篇文章