Unity 2D横版闯关游戏 (JUNGLE RULES)
用Unity做的2D横版闯关游戏,简单叙述思路,分享脚本编写
目录
<项目源码>
<游戏演示>
<项目内容>
(1) 项目内容
JUNGLE RULES是一款Made With Unity2D 的横版闯关游戏。主要能实现以下功能:
1.人物的控制:A键向左跑动,D键向右跑动,Q键进行攻击模式切换,J键进行攻击(持刀模式下为劈斩,持枪模式下为射击),K键进行跳跃(可进行二段跳),L键读取告示板。人物受击时扣血,血槽空时死亡,死亡后再当前关卡重生。持刀攻击到小怪时产生刀锋效果,持枪攻击到小怪时产生爆炸效果。
2.简单的小怪AI:两类怪物,一类持剑的小怪,一类非持剑的小怪。两类小怪均能实现在指定范围内随机游走的功能。当玩家进入小怪警戒范围后,小怪自动向玩家靠近。玩家碰到小怪即掉血,持剑小怪靠近人物时同步进行挥剑劈砍。小怪受击红色闪烁并扣血,小怪死亡时消失。
3.UI界面:主界面显示游戏题目,开始游戏(PLAY)按钮和退出游戏(QUIT)按钮,均能进行逻辑的交互。游戏界面左上方显示人物血条和子弹数量,会随具体数值变化。游戏结束界面显示结束语和退出游戏按钮。
4.关卡的搭建:游戏主要有两个关卡,新手村关卡和主要关卡。新手村关卡搭建简单地图。主要关卡设计成迷宫形式。玩家在可站立的地图块上进行操作,若掉出地图死亡重生。
5.其它对象及逻辑:告示板——人物靠近告示板按下L键能显示告示内容。传送阵——人物进入传送阵后自动进入下一个关卡。游戏相机——自动跟随人物,小怪受击时相机震动。
<项目分析>
(1) 游戏策划
1.1游戏剧情:
森林里出现了许多带头盔的绿色怪物,威胁到了人们的生存。为此,猎人带着刀枪向森林出发,打败小怪,还给人们平静的生活。
1.2游戏关卡:
游戏分为两个关卡。为了引导新玩家慢慢熟悉游戏操作,在新手村关卡搭建简单的地图,置放持剑小怪和非持剑小怪各一只,让玩家了解小怪的攻击模式。新手村关卡设置给予玩家操作提示信息的告示板,玩家可以在需要时进行读取。当玩家适应完操作后,可以自主选择进入传送阵到达主关卡。
主关卡搭建复杂的地图,在相机视角受限的情况下,玩家无法了解到整张地图的信息,只能慢慢探索。在主关卡各处设置多只小怪,增加游戏难度。另外,玩家还将面临掉出地图死亡的风险,在地图的设计上需要能体现出这样的元素。当玩家完成探索后即为为森林带来了和平,玩家通过位于关卡深处的传送阵进入结束场景,游戏结局。
1.3游戏平衡:
人物的生命值设置为30,人物最大载弹量设置为7,角色攻击伤害设置为2,小怪的生命值均设置为10,非持剑小怪的伤害设置为1,持剑小怪的伤害设置为3。总体上游戏难度偏简单,不给予玩家过大的操作压力。虽然持剑小怪攻击频率较快,伤害也高,但只要玩家持枪攻击便可无伤击败。
(2) 游戏美术
游戏整体美术风格为像素风。游戏主题为猎人,森林,因此在素材的选择上多采用绿色。菜单场景,新手村场景,主场景,结束场景均应用不同的背景图片。利用地图块搭建地图,并用农场主题的素材包进行装饰。
游戏素材均来自于Unity官方资源商店和网络交易平台,有偿获得,不存在美术版权的侵犯问题。
(3) 游戏程序
3.1主要涉及的对象及功能。
3.1.1游戏角色(Player)
游戏角色自身具备的属性值为:移动速度,跳跃速度,人物生命值,攻击伤害。
游戏角色的五项动作为:待机,跑动,跳跃,掉落,攻击。首先在显示层,我们需要利用控制人物动画的Animator组件实现动画的切换。然后在逻辑层面,我们需要在C#脚本中编写相关函数,当系统收到键盘相应的输入并且满足条件时,调用函数执行相关逻辑,最终实现一个整体视觉效果上的动作的实现。
游戏角色作为游戏中最重要的组成部分,和游戏中的各类物体都有交互。我们需要检测游戏和物体的碰撞,然后触发相应的函数。人物和小怪碰撞时,调用人物受伤的函数,子弹以及刀剑的碰撞框和小怪碰撞时,调用小怪的受伤函数。人物和传送门碰撞时,调用传送函数进入下一个场景。人物和公告板碰撞时,按L调用显示公告信息的函数。
3.1.2小怪(Enemy)游戏小怪自身具备的属性为:移动速度,小怪生命值,小怪伤害。
游戏小怪的三项动作为:待机,跑动,攻击。同样在显示层利用Animator组件进行动画的切换。在逻辑层用C#脚本控制Animator组件进行切换。
游戏有两种不同的小怪,但两只小怪在共有的基本逻辑的实现上是类似的,我们编写抽象基类Enemy,在Enemy的基础上继承出持剑小怪(EnemySK)和非持剑小怪(EnemyNSK)。在基类中实现小怪被击受伤,击打到人物人物受伤,小怪受伤红色闪烁三种基本逻辑。在派生类的脚本中对小怪动画切换逻辑和随机游走进行编写。
3.1.3 UI界面(Canvas)
在主界面中显示游戏主题“JUNGLE RULES”,引导语“Defeat Enemies And Save People!”引导玩家进入基本的游戏剧情。主界面设置两个按钮,单击开始游戏(PLAY)按钮,进入新手村场景。单击退出游戏(QUIT)按钮,退出程序。游戏界面能够获取人物的生命值,子弹数两项参数,动态地变化血量槽和子弹数槽。结束界面中显示结束语“Appreciation To Your Bravery”为玩家的勇气点赞,设置退出按钮,单击退出游戏(QUIT)按钮,退出程序。
3.1.4 摄像机(Main Camera)
摄像机始终跟随人物显示地图画面,确保人物始终在画面居中偏下的位置。与此同时,摄像机不能超出地图边界,显示空白画布的内容。当人物跳出地图时,摄像机被限制在边界范围内。摄像机也有Animator组件,小怪受击时实现相机震动动画。
3.1.5 告示板(Billboard)和传送阵(Trans)
这两个对象实现逻辑类似,都是利用了碰撞体。告示板检测到和人物的碰撞且键盘输入L时显示告示信息。人物离开后告示信息自动消失。传送阵利用Unity引擎的场景管理类(SceneManagement)进行场景的跳转。
3.2场景的划分
游戏主要分为主菜单场景,新手村场景,主关卡场景,结束场景四个场景,顺序进行。
<项目实现>
(1) 游戏角色(Player)
//Player Controller(Script)
public class PlayerController : MonoBehaviour
{
public float runSpeed;//人物奔跑速度
public float jumpSpeed;//人物第一次弹跳速度
public float doubleJumpSpeed;//人物第二次弹跳速度
private Rigidbody2D myRigidbody;//刚体组件
private Animator myAnim;//动画组件
private BoxCollider2D myFeet;//方形碰撞体组件
private bool isGround; //记录脚是否接触地面
private bool canDoubleJump;//判断能否进行二段跳
// Start is called before the first frame update
void Start()
{
myRigidbody = GetComponent<Rigidbody2D>();//调用对象的刚体组件
myAnim = GetComponent<Animator>();//调用对象动画组件
myFeet = GetComponent<BoxCollider2D>();//调用对象方形碰撞体组件
}
// Update is called once per frame
void Update()
{
Run();//实现人物左右跑动和动画显示
Flip();//左右跑动时素材水平翻转
Jump();//实现人物跳跃逻辑
CheckGrounded();//检测人物是否接触地面
SwitchAnimation();//实现人物跳跃时的动画切换
SwitchMode();//切换攻击模式
}
//Player Health(Script)
public class PlayerHealth : MonoBehaviour
{
public int Health;//人物生命值
private Animator myAnim;//动画组件
// Start is called before the first frame update
void Start()
{
Healthbar.HealthMax = Health;//设置UI界面的生命条
Healthbar.HealthCurrent = Health;//设置UI界面的生命条
myAnim=GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
}//调用Player的动画组件
// Update is called once per frame
void Update()
{
if (Health <= 0)
{
Invoke("Restart", 3f);//死亡在3秒后重生
}
if (transform.position.y < -20)//掉出地图判断为死亡
{
Health = 0;
Healthbar.HealthCurrent = Health;
}
}
public void DamagePlayer(int damage);//人物受伤函数,由小怪调用
void Restart();//重生函数
}
PlayerAttack用于实现人物持刀攻击,BulletHit用于实现人物持枪攻击
//Player Attack(Script)
public class PlayerAttack : MonoBehaviour
{
public int damage;//攻击伤害
public float time; //攻击动作持续时间
public float startTime;//攻击动作开始时间
public GameObject SwordPrefab;//攻击效果
private Animator anim;//动画组件
private PolygonCollider2D collider2D;//多边形碰撞体组件
private bool CanAttack;//能否攻击(一次攻击完之后才能进行下一次攻击)
// Start is called before the first frame update
void Start()
{ //调用Player动画组件
anim = GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
collider2D = GetComponent<PolygonCollider2D>();//调用碰撞体组件
CanAttack = true;
}
// Update is called once per frame
void Update()
{
Attack();//攻击实现函数
}
Attack()//检测输入 调用协程
IEnumerator StartAttack()//协程 等待一定时间激活攻击碰撞框
IEnumerator disableHitBox()//协程 等待一定时间取消攻击碰撞框
void OnTriggerEnter2D(Collider2D other)//检测到碰撞调用小怪受伤函数
}
//Bullet Hit(Script)
public class BulletHit : MonoBehaviour
{
private int Bullet = 7;//设置最大子弹数
private bool canShoot = true;//标记是否有子弹
public GameObject BulletPrefab;//子弹预制体
private Animator anim;//动画组件
// Start is called before the first frame update
void Start()
{
Bulletbar.BulletMax = Bullet;//设置UI界面的子弹槽
Bulletbar.BulletCurrent = Bullet;//设置UI界面的子弹槽
anim = GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
//调用player的动画组件
}
// Update is called once per frame
void Update()
{
if (Input.GetButtonDown("Attack") && anim.GetBool("Armed"))//持枪状态下按J
{
if (Bullet > 0)//有子弹
{
anim.SetTrigger("Attack");//设置攻击动画
Instantiate(BulletPrefab, transform.position, transform.rotation);
//在枪口处产生子弹
Bullet -= 1;//子弹数减1
}
}
if (Input.GetButtonDown("Loading"))//实现装弹
{
Bullet = 7;
}
Bulletbar.BulletCurrent = Bullet;//实时给UI界面传子弹数
}
}
//Bullet(Script)
public class Bullet : MonoBehaviour
{
public float speed;//子弹飞行速度
public int damage;//子弹伤害
public float destroyDistance; //子弹最长飞行距离
public float time;//产生爆炸效果并在time秒后消除
public GameObject ImpactEffect;//子弹爆炸效果
private Rigidbody2D rb2d;//刚体组件
private Vector3 startPos;//三维向量记录子弹出射位置
// Start is called before the first frame update
void Start()
{
rb2d = GetComponent<Rigidbody2D>();//调用对象刚体组件
rb2d.velocity = transform.right * speed;//设置飞行速度
startPos = transform.position;
}
// Update is called once per frame
void Update()
{
float distance = (transform.position - startPos).sqrMagnitude;//计算已飞行的距离
if (distance > destroyDistance)//超出飞行距离自动消除
{
Destroy(gameObject);
}
}
void OnTriggerEnter2D(Collider2D other)//碰撞到小怪调用小怪受伤函数
//产生子弹爆炸效果
}
(2) 小怪(Enemy)
//Enemy(Script)
public abstract class Enemy : MonoBehaviour
{
public int Health;//小怪生命值
public int damage;//小怪伤害
public float flashTime;//红色闪烁时间
public float DeathTime;//死亡动画播放时间
private SpriteRenderer sr;//图像渲染组件
private Color originalColor;//初始颜色
private PlayerHealth playerHealth;//玩家生命值
// Start is called before the first frame update
public void Start()
{
playerHealth = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerHealth>();//获取生命
sr = GetComponent<SpriteRenderer>();//调用组件
originalColor = sr.color;//保存初始色彩
}
// Update is called once per frame
public void Update()
{
}
public void TakeDamage(int damage) //小怪受伤函数
void FlashColor(float time) //小怪红色闪烁函数
void ResetColor()//重新设置为原来的颜色
void OnTriggerEnter2D(Collider2D other)//碰撞到人物人物扣血
}
//Knight(Script)
public class Knight : Enemy
{
public float speed;//小怪移动速度
public float startWaitTime;//小怪等待时间
private float waitTime;//记录到达某点已经过的时间
private Rigidbody2D myRigidbody;//刚体组件
public Transform movePos;//移动位置
public Transform leftPos;//巡逻范围最左端
public Transform rightPos;//巡逻范围最右端
private Transform playerTransform;//人物位置
private Animator myAnim;
private BoxCollider2D Bcollider2D;
// Start is called before the first frame update
public void Start()
{
base.Start();//调用基类的Start()函数
waitTime = startWaitTime;//初始化等待时间
movePos.position = GetRandomPos();//初始化小怪移动位置为随机位置
myAnim = GetComponent<Animator>();
Bcollider2D = GetComponent<BoxCollider2D>();
playerTransform = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();//获得人物位置
}
// Update is called once per frame
public void Update()
{
base.Update();//调用基类的Update()函数
if (Health > 0)
{
Guard();//进行巡逻
}
else
{
myAnim.SetTrigger("Die");//调用死亡动画
Bcollider2D.enabled = false;//小怪碰撞体失效
Invoke("KillEnemy", DeathTime);//死亡动画放完 销毁物体
}
}
void KillEnemy()//销毁物体
Vector2 GetRandomPos()//获得随机位置
void Flip()//小怪左右游走时小怪图像的水平翻转
void Guard()//随机游走 追击角色实现函数
}
(3) UI界面
//Health bar(Script)
public class Healthbar : MonoBehaviour
{
public Text HealthText;//UI上显示的生命值
public static int HealthCurrent;//当前生命
public static int HealthMax;///最大生命
private Image healthbar;//生命条图像
// Start is called before the first frame update
void Start()
{
healthbar = GetComponent<Image>();//调用生命条
HealthCurrent = HealthMax;
}
// Update is called once per frame
void Update()
{
healthbar.fillAmount = (float)HealthCurrent / (float)HealthMax;//设置生命条填充量
HealthText.text = HealthCurrent.ToString() + "/" + HealthMax.ToString();//设置生
命值
}
}
//Bullet Bar(Script)
public class Bulletbar : MonoBehaviour
{
public Text BulletText;//UI上显示的子弹数
public static int BulletCurrent;//当前子弹数
public static int BulletMax;//最大子弹数
private Image bulletbar;//子弹条图像
// Start is called before the first frame update
void Start()
{
bulletbar = GetComponent<Image>();//调用子弹条
//HealthCurrent = HealthMax;
}
// Update is called once per frame
void Update()
{
bulletbar.fillAmount = (float)BulletCurrent / (float)BulletMax;//设置子弹条填充量
BulletText.text = BulletCurrent.ToString() + "/" + BulletMax.ToString();//设置子
弹值
}
}
(4) 摄像机(Camera)
Camera Follow(Script):
public class CameraFollow : MonoBehaviour
{
public Transform target;//跟踪目标 (设置为Main Camera)
public float smoothing;//平滑常数
public Vector2 minPosition;//跟踪边界左下方
public Vector2 MaxPosition;//跟踪边界右上方
// Start is called before the first frame update
void Start()
{
GameController.camShake = GameObject.FindGameObjectWithTag("CameraShake").GetComponent<CameraShake>();
}
void LateUpdate()//随后触发
{
if (target != null)
{
if (transform.position != target.position)//实现跟随
{
Vector3 targetPos = target.position;
targetPos.x = Mathf.Clamp(targetPos.x, minPosition.x, MaxPosition.x);
targetPos.y = Mathf.Clamp(targetPos.y, minPosition.y, MaxPosition.y);
transform.position = Vector3.Lerp(transform.position, targetPos, smoothing);
}
}
}
public void SetCamPosLimit(Vector2 minPos, Vector2 maxPos)
{
minPosition = minPos;
MaxPosition = maxPos;
}
}
(5) 公告板(Billboard)
//Sign(Script)
public class Sign : MonoBehaviour
{
public GameObject dialogbox;//公告板
private bool isPlayerInSign;//标记人物和公告板的碰撞
// Start is called before the first frame update
void Start()
{
dialogbox.SetActive(false);//设置公告信息不可见
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.L) && isPlayerInSign)
{
dialogbox.SetActive(true);//设置公告信息可见
}
}
void OnTriggerEnter2D(Collider2D other)//检测碰撞 给isPlayerInSign赋值
void OnTriggerExit2D(Collider2D other)//离开碰撞体 设置公告信息不可见
//isPlayerInSign同步变化
}
(6) 游戏场景(Scene)
<项目源码>
更多推荐
所有评论(0)