ReverseMan

期末大实验Unity游戏制作

一. 摘要

​ 借鉴于经典小游戏《反转重力战士》,主角具有反转自身重力的能力。本文利用游戏引擎Unity实现了主要功能,包括:1.角色逻辑实现 2.关卡设计 3.UI。下面将一一介绍。

二. 功能模块

1. 角色逻辑实现

1.1 角色主体
image-20231226162114630

​ 使用了unity默认的正方形sprite作为角色主体

1.2 组件概述
image-20231226163940031

​ 角色主体附加了多个组件,包括:默认的Transform、Sprite Render以及附加的2个Box Collider 2D、1个Rigidbody 2D和自定义的C#脚本Player Control。

1.3 组件详解
1.3.1 Rigidbody 2D组件
image-20231226164344621

​ 该组件中文名为2D刚体组件,本游戏中将会用到两个该组件中的功能——Body Type和Constraints。

​ Body Type功能有三个选项Dynamic、Kinematic和Static,游戏中只用到第一和第三两个功能。Dynamic表示该对象动态性地与所有物体交互,适用于游戏主体,而Static表示该对象完全不动且只与Dynamic物体交互,将会用作地面的制作。

​ Constrains中包括三个选项框,分别冻结x、y、z三个轴。这里选择冻结z轴避免主角移动时翻滚。

1.3.2 Box Collider 2D
image-20231226165544125 image-20231226170337427

​ Box Collider 2D是一个盒型的碰撞体组件,控制主角与环境的碰撞。游戏中使用的功能主要是IsTrigger。该功能决定碰撞器是否作为触发器使用。碰撞器用于与环境的物理交互,触发器用于特殊的检测(如捡拾金币)。使用两个Box Collider 2D的原因在于各使用一个盒型碰撞器和一个触发器。

​ 此处触发器的大小为(0.8 , 1.01)是避免因多次触发产生的BUG。

1.3.3 C#脚本-PlayerControl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerControl : MonoBehaviour
{
    private Rigidbody2D rb;
    private Collider2D coll;
    private Transform trans;

    public GameObject camera;

    private bool jumpAction = false;
    private bool isReverse = false;
    public static int gameStatus = 0;

    public float speedStep = 100;
    public float speed;
    public float jumpForce = 100;
    public int jumpCount = 1;
    public static int grades = 0;
    static public float interval = 0;




    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        coll = GetComponent<Collider2D>();
        trans = GetComponent<Transform>();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetButtonDown("Jump")&&jumpCount>=1)
        {
            jumpAction = true;
        }
        interval -= Time.deltaTime;
        if (Input.GetButtonDown("Reverse")&&interval <= 0)
        {
            isReverse = true;
            interval = 1.5f;
        }
         GameFail();
    }
     void FixedUpdate()
    {
        MoveMent();
        Jump();
        Reverse();
        
    }



    void MoveMent()
    {
        if (Input.GetAxis("Horizontal") != 0)
        {
            speed = Input.GetAxis("Horizontal") * speedStep * Time.deltaTime;
            rb.velocity = new Vector2(speed, rb.velocity.y);
        }
        else
        {
            rb.velocity = new Vector2(rb.velocity.x * 14/15, rb.velocity.y);
        }
    }
    void Jump()
    {
        if (jumpAction && jumpCount >= 1)
        {
            jumpCount--;
            jumpAction = false;
            if (rb.gravityScale >= 0)
                rb.velocity = new Vector2(rb.velocity.x, jumpForce * Time.deltaTime);
            else
                rb.velocity = new Vector2(rb.velocity.x, -1 * jumpForce * Time.deltaTime);
        }
    }
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "Ground")
        {
            jumpCount = 1;
            rb.velocity = new Vector2(rb.velocity.x, 0);
        }
        if (collision.gameObject.tag == "Gift")
        { 
       	   Destroy(collision.gameObject);
            grades += 10;
        }
        if(collision.gameObject.tag=="Gate")
        {
            gameStatus = 1;
        }
    }
    void Reverse()
    {
        if (isReverse)
        {
            rb.velocity = new Vector2(rb.velocity.x, 0);
            rb.gravityScale *= -1;
            isReverse = false;
        }
    }
    void GameFail()
    {
        if (trans.position.x < camera.transform.position.x - 25 || trans.position.y < -7 ||trans.position.y > 20 )
        {
            gameStatus = -1;
        }
    }

}

​ 顾名思义,Unity使用C#作为脚本语言实现。每一个Unity中的C#脚本都相当与一个MonoBehaviour的派生类,从而可以调用许多便利的内置成员函数。它包括两个基本的函数 Start()、Update()。前者负责开始阶段执行一次,后者以固定的时间不断执行。还有一个FixedUpdate(),它与Update()很像,但是以固定的帧率执行,适用于不同的计算机。还有一个特殊的函数 private void OnTriggerEnter2D(Collider2D collision) ,该函数会使用上文的触发器并执行内部代码。

​ Start()函数中使用了 GetComponent()模板函数,可以在列表窗口中寻找相对应的组件并赋值给相应定义的变量,使得这些组件可被脚本编程。

​ Update()函数中有三个关于游戏状态控制的代码块。第一个if中的Input.GetButtonDown(“Jump”)&&jumpCount>=1表示当按下键“Jump”(默认为“W”)且jumpCount>=1时将jumpAction变为true。第二个if前使用了一个计时器 interval -= Time.deltaTime;作为代码块内容的CD,if中的判断条件Input.GetButtonDown(“Reverse”)&&interval <= 0表示按下“Reverse”键(默认为“SPACE”)并且计时器小于0时将isReverse设置为true并重置计时器。第三个函数是一个实时判断游戏主角状态的函数,如果玩家位置超出摄像机边界则将gameSta-tus设置为-1,视为游戏失败。

​ FixedUpdate()里面有三个函数,按固定帧率执行MoveMent()、Jump()、Reverse()。

​ MoveMent()函数使用了if (Input.GetAxis(“Horizontal”) != 0)分支控制流程。Input.GetAxis(“Horizontal”)接受“A”和“D”的键入并映射到一个范围为-1到1的值(不键入即为0)。若玩家按下“A”或“D”,则执行第一个代码块中的代码,为speed赋值,并将其作为主角沿x轴的速度。乘以TIme.deltaTime保证速度为帧间隔的倍数,以防频闪的发生。否则执行第二个代码块中的内容,每帧将x方向上的速度变为14/15,达到滑步的效果。

​ Jump()首先检查jumpAction的状态,如为true则执行代码。进入Jump()后首先重置了jumpAction状态并使jumpCount减一,再根据角色的Gravityscale判定向上跳还是向下跳。

​ OnTriggerEnter2D(Collider2D collision)函数在其他碰撞体进入触发器时触发,然后通过collision.gameObject.tag判断是碰撞物体的标签(tag),如果是“Ground”则表示触地,应当使jumpAction为false表示结束跳跃,同时使jumpCount加一;如果是“Gift”则表示捡拾金币,应该使分数grade增加;如果是“Gate”则表示玩家到达出口,将gameStatus设置为1表示游戏胜利。

2. 关卡设计

2.1 StartScene
image-20231226231816823

​ 开始场景的只设计了UI,鼠标点击按钮即可跳转关卡,具体实现在UI中详述。

2.2 Level1
2.2.1 UI

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​ 图中UI包括左上角的GRADES:,捡拾金币可以显示加分;右下角的Reverse指示器,当按下“SPACE”将反转并变成红色。具体实现同样在UI中详述。

2.2.2 相机

​ 游戏的相机会以一定速度向右平移,促使玩家的操作。这里使用C#脚本实现。

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

public class Camera : MonoBehaviour
{
    public int defalutSpeed = 500;
    private Rigidbody2D rb;
    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody2D>();   
    }

    // Update is called once per frame
    void Update()
    {

    }
    private void FixedUpdate()
    {
        rb.velocity = new Vector2( defalutSpeed * Time.deltaTime ,rb.velocity.y);
    }
}

​ 这里使用GetComponent()获取刚体组件,并在FixedUpdate()中实时赋给相机一个恒定的x轴速度,使用Time.deltaTime保证画面连续性。

2.2.3 地面Ground

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-20231227144017619

​ 图中的黑色部分为Ground,它使用了Static的Rigidbody 2D组件,并附带一个Box Collider 2D组件,标签被设置为ground,以便于主角交互。

2.2.4 金币Gift

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-20231227144111755

​ 黄色的圆就是金币,它只有一个作为触发器的Circle Collider 2D,但是标签被设置为Gift,以便于主角交互。

2.2.5 出口Gate

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

image-20231227144851674

​ 本体为白色的矩形Sprite,附加了一个作为触发器的Box Collider 2D,同时标签被设置为Gate,以便主角交互。

2.3 EndScene

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​ 与StartScene相同,该场景只有UI,详解于下。

2.4 UI
2.4.1 画布
image-20231227145417863

​ Unity中要使用UI画布是必不可少的,它相当于一个映射到玩家屏幕上的标准场景。它可以附加多个与UI相关的组件,如文本、按钮、C#脚本。

2.4.2 文本
image-20231227145848859

​ 文本组件可以在画布上显示输入的文本,并根据设置详细对字体、文本效果等个性化定制。

2.4.3 按钮
image-20231227150019244

​ 按钮组件可以检测鼠标的点击,其内置了一个text脚本可以用C#脚本调用。

2.4.4 C#脚本-按钮
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ButtonStartGame : MonoBehaviour
{
    public Button btn;
    public Text txt;

    void Start()
    {
        btn = GetComponent<Button>();
        txt = btn.transform.Find("Text").GetComponent<Text>();
        txt.fontSize += 50;
        if (PlayerControl.gameStatus == 0)
        {
            txt.text = "START GAME";
        }
        else
            txt.text = "RETRUN MENU";
    }
    public void OnMouseUpAsButton()
    {
        if (PlayerControl.gameStatus == 0)
        {
            PlayerControl.grades = 0;
            Application.LoadLevel(1);
        }
        else
        {
            PlayerControl.gameStatus = 0;
            Application.LoadLevel(0);
        }
    }
    
}

​ 该脚本通过检测PlayerControl内的静态变量gameStatus的值来判断当前所处的场景并输出相应的Text文本。

​ 它还使用了一个OnMouseUpAsButton()函数,在鼠标点击时调用并使用Application.LoadLevel()函数切换到其他关卡。

2.4.5 Level1
image-20231227151340306 image-20231227151643586 image-20231227151458642

​ 该UI本质是一个image,通过附加的C#脚本改变箭头方向与颜色。

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

public class GameUI : MonoBehaviour
{
    public RectTransform rTrans;
    private Image ima;
    private bool isReverse = false;

    // Start is called before the first frame update
    void Start()
    {
        rTrans = GetComponent<RectTransform>();
        ima = GetComponent<Image>();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetButtonDown("Reverse")&&PlayerControl.interval <= 0)
            isReverse = true;
    }
    private void FixedUpdate()
    {
        Reverse();
        ColorJudge();
    }
    void Reverse()
    {
        if (isReverse)
        {
            rTrans.localScale = new Vector3(1, -1 * rTrans.localScale.y, 1);
            isReverse = false;
        } 
    }
    void ColorJudge()
    {
        if (rTrans.localScale.y > 0)
            ima.color = new Color(255, 255, 255);
        else
            ima.color = new Color(255, 0, 0);
    }

    
}

​ 该脚本也定义了Reverse()函数,通过相同的检测调用,并反转当前的localScale改变箭头方向。

​ 该脚本还定义了ColorJudge()函数,通过对localScale.y的检测改变image的颜色。

三. 总结

1. 游戏示例

1.1 游戏开始
image-20231227152233644
1.2 游戏界面
image-20231227152338195
1.3 游戏结束
image-20231227152406234 image-20231227152514141

2. 总结

​ 本次作业完成了一个小游戏制作的基本流程,但未对核心机制Reverse做出更有趣的应用。在脚本部分并不复杂,主要是对游戏引擎Unity操作的熟悉。

1.3 游戏结束
image-20231227152406234 image-20231227152514141

2. 总结

​ 本次作业完成了一个小游戏制作的基本流程,但未对核心机制Reverse做出更有趣的应用。在脚本部分并不复杂,主要是对游戏引擎Unity操作的熟悉。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值