【Unity3D】回合制游戏

2023-11-13

回合制游戏一直在游戏史,至少是在中国的游戏历史上扮演很重要的角色。从仙剑到梦幻,这类游戏深受玩家喜爱。那么在Unity3D中怎么实现呢?下面用一个比较简单Unity3D的一对一回合制游戏来说明这个问题。其实也不难,关键是理清各个处理关系。

如下图所示,绿色代表玩家操控的主角,蓝色代表遇到的敌人,分别赋予大家100HP,然后玩家打敌方一下,敌方就-40HP,玩家被敌方摸一下就-30HP。下面是玩家成功战胜敌人的情况。


当然,玩家也可以防御的,此时敌方摸玩家一下仅15HP。下图是展示玩家HP变成0,游戏失败的情况。


当然,这个例子一点不好玩,毕竟又没有药品,招式只有1个,还是1对1的对打。甚至连MP都没有。也没有根据速度计算谁先出手的问题,还有Buff与Debuff之类的。不过,为了说明在Unity3D如何制作回合制游戏。我尽可能将一些能简化的东西先简化,主要突出回合制游戏的制作核心。

一、场景布置

首先是简单的场景布置,在3D部分很简单。就几个简单的基本组件,在一个Plane上面放2个Cube。并且上不同颜色的纯色Material。不赘述了,不懂可以参考《【Unity3D】物体、材质的设置、物体位移与旋转》(点击打开链接)。唯一需要大家注意的是,请将两个Cube改好名,以免到时候编程不知道哪个跟哪个。


其次是UGUI的布置。左下角是一个名为ActionPanel的Panel,旗下有两个按钮Attack Button和Defend Button,一会儿ActionPanel将被控制,而按钮Attack Button和Defend Button则将赋予点击事件。UGUI的按钮点击事件可以参考《【Unity3D】场景切换、UGUI组件事件、开始游戏与关闭游戏》(点击打开链接)。在这个ActionPanel的下方则是一个名为PlayerHPinfo的Text,同样会被脚本控制,用于显示血量等信息。

至于右上角是个动态文本的滚动区域WarinfoPanel,里面放置的一个WarinfoText用于显示战斗信息的文本,具体的制作可以参考《【Unity3D】动态文本的滚动条》(点击打开链接),这里需要注意上Mask组件的时候去除Show Mask Graphic,不然WarinfoText显示不出来。而在其下方,则是一个退出战斗的按钮ExitButton,当然这个东西,在实际游戏里面完全可以不要,自动切换回战斗前的场景。


并同时新建一个空物体WarControl,赋予脚本WarControl.cs。

以下是各个对象的从属关系,请注意改好名字。因为基本上上面提到的组件,都将被WarControl.cs控制。


二、脚本编写

WarControl.cs设置的变量,并且要控制的物体如下所示:


这段代码的思想如下:

由于Update()在每一帧的刷新都被执行的,在1秒就30帧的瞬间,Update()里面的代码不读完,这游戏就被卡死,所以Update()这个可视为主线程的函数,只承担以下简单任务,时刻在判断HP是否见底。


而攻击表演这些要交代给玩家看的东西,至少要占用1s的技能表演,我们则通过协程Coroutine完成,协程的详细说明具体可以看《【Unity3D】协程Coroutine的运用》(点击打开链接)。协程,其实也就是Unity3D的子线程,将通过按钮点击时间来创建。各个按钮点击之后,具体的思想如下图表示,其中实线表示玩家点击了“攻击按钮”,虚线则表示玩家点击了“防御按钮”。上例子的动画,我采用了Unity3D中极其简单的动画组件iTween来做,具体可以见《【iTween】单点移动和旋转》(点击打开链接)。

这里涉及到挂起0.5s~0.9s的东西,因此,只能写在协程里面完成的,不可能写在Update()里面,不然这游戏绝对卡死。


因此,WarControl.cs如下,赋予给空物体WarControl。

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

public class WarControl : MonoBehaviour
{

    public GameObject Player;//代表玩家的绿色立方体
    private int Player_HP;//玩家的HP
    public GameObject Enemy;//代表玩家的蓝色立方体
    private int Enemy_HP;//敌人的HP
    public GameObject ActionPlane;//左下角玩家操作面板,旗下有两个按钮
    public GameObject PlayerHPinfo;//左下角的玩家的HP信息文本Text
    public GameObject WarinfoText;//右上角的战斗信息文本Text
    public GameObject ExitButton;//退出按钮
    private int Player_Max_HP;//玩家最大血量,这个其实可以视为一个常量const

    /*场景初始化过程,数据初始化过程我也写在这里了*/
    void Start()
    {

        Time.timeScale = 1;//打破时间结界,主要是配合下面update()中结算时的布置的时间结界Time.timeScale = 0;玩家点击“退出”重新进入场景

        /*定义玩家和敌人的血量和玩家的最大血量,这部分在实际中,可以从记载游戏状态的xml等地方取,这里粗暴定义为100*/
        Player_HP = 100;
        Enemy_HP = 100;
        Player_Max_HP = Player_HP;

        /*更新UI*/
        PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP;//玩家HP信息文本的更新
        WarinfoText.GetComponent<Text>().text = "战斗开始!\n";//战斗信息更新

        ExitButton.SetActive(false);//隐藏“退出战斗”这个按钮

    }

    /*主线程,时刻在读取,这段由于大量代码是相同的,因此还可以优化下这个条件结构的写作*/
    void Update()
    {

        if (Player_HP < 0)
        {
            PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP;//由于战斗结算在下面的子线程完成,在最终的战斗结算需要再次更新UI,以免有显示BUG
            Time.timeScale = 0;//布置一个时间结界
            ExitButton.SetActive(true);//打开“退出游戏”按钮
            Player.SetActive(false);//将代表玩家这个立方体消失,实际上还可以播放下玩家死亡动画什么的
            ActionPlane.SetActive(false);//关闭操作UI
            /*更新战斗信息*/
            WarinfoText.GetComponent<Text>().fontSize = 30;
            WarinfoText.GetComponent<Text>().text = "玩家死亡!战斗失败!\n";
        }

        if (Enemy_HP < 0)//同上,不赘述了
        {
            PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP;
            Time.timeScale = 0;
            ExitButton.SetActive(true);
            Enemy.SetActive(false);
            ActionPlane.SetActive(false);
            WarinfoText.GetComponent<Text>().fontSize = 30;
            WarinfoText.GetComponent<Text>().text = "敌人死亡!胜利战斗!\n";
        }

    }

    /*按钮点击事件*/
    public void AttackButtonOnclick()
    {
        StartCoroutine(Attack());
    }
    public void DefendButtonOnclick()
    {
        StartCoroutine(Defend());
    }
    public void ExitButtonOnclick()
    {
        Application.LoadLevel("Turnbase_Single");
    }

    /*攻击协程*/
    IEnumerator Attack()
    {
        ActionPlane.SetActive(false);//先关闭操作UI
        StartCoroutine(Player_Attack());//新建一条玩家攻击协程
        yield return new WaitForSeconds(0.9f);//等待0.9s再读下面的代码,也就是等待玩家攻击技能表演完,一共0.9s
        yield return new WaitForSeconds(0.5f);//再等待0.5s,让玩家喘口气,表示上述动作交代完了,开始交代下述敌人攻击的技能
        StartCoroutine(Enemy_Attack(false));//再新建一条敌人攻击的协程,这里的false代表玩家没有防御
        yield return new WaitForSeconds(0.9f);//等待0.9s再读下面的代码,也就是等待敌人攻击技能表演完,一共0.9s
        PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP;//更新UI
        ActionPlane.SetActive(true);//再打开操作UI,让玩家进行下一个回合的指令
        yield return null;
    }

    /*防御协程*/
    IEnumerator Defend()
    {
        ActionPlane.SetActive(false);//先关闭操作UI
        StartCoroutine(Enemy_Attack(true));//新建一条敌人攻击的协程,这里的true代表玩家没有防御
        yield return new WaitForSeconds(0.9f);//等待0.9s再读下面的代码,也就是等待敌人攻击技能表演完,一共0.9s
        PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP;//更新UI
        ActionPlane.SetActive(true);//再打开操作UI,让玩家进行下一个回合的指令
        yield return null;
    }

    /*玩家攻击技能的表演,用iTween实现*/
    IEnumerator Player_Attack()
    {
        iTween.MoveTo(Player, iTween.Hash("position", new Vector3(0, 0.5f, 2), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none"));
        yield return new WaitForSeconds(0.3f);
        iTween.RotateTo(Player, iTween.Hash("rotation", new Vector3(0, 180, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none"));
        iTween.RotateTo(Enemy, iTween.Hash("rotation", new Vector3(30, 0, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none"));
        yield return new WaitForSeconds(0.3f);
        iTween.MoveTo(Player, iTween.Hash("position", new Vector3(0, 0.5f, -4), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none"));
        iTween.RotateTo(Enemy, iTween.Hash("rotation", new Vector3(0, 0, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none"));
        iTween.RotateTo(Player, iTween.Hash("rotation", new Vector3(0, 0, 0), "easeType", "easeInCubic", "time", 0f, "loolType", "none"));
        yield return new WaitForSeconds(0.3f);

        /*攻击结算*/
        WarinfoText.GetComponent<Text>().text += "玩家攻击,敌人-40HP\n";
        this.Enemy_HP -= 40;

    }

    /*敌人攻击技能的表演,用iTween实现*/
    IEnumerator Enemy_Attack(bool isPlayerDefend)
    {
        iTween.RotateTo(Enemy, iTween.Hash("rotation", new Vector3(0, 180, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none"));
        iTween.RotateTo(Player, iTween.Hash("rotation", new Vector3(-30, 0, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none"));
        yield return new WaitForSeconds(0.3f);
        iTween.RotateTo(Enemy, iTween.Hash("rotation", new Vector3(0, 0, 0), "easeType", "easeInCubic", "time", 0f, "loolType", "none"));
        iTween.RotateTo(Player, iTween.Hash("rotation", new Vector3(0, 0, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none"));
        /*攻击结算*/
        if (!isPlayerDefend)
        {
            WarinfoText.GetComponent<Text>().text += "敌人攻击,玩家-30HP\n";
            this.Player_HP -= 30;
        }
        else
        {
            WarinfoText.GetComponent<Text>().text += "敌人攻击,玩家-15HP\n";
            this.Player_HP -= 15;
        }
        yield return null;
    }

}

也就一百多行代码而已!这里的攻击动画,用到了iTween实现,具体可以看《【iTween】利用协程完成多个动作、iTween的动作序列》( 点击打开链接),不赘述了。同时,这里的玩家死亡和敌人死亡其实也可以加入一个立方体碎裂的动画,让游戏更加生动,具体可以参考《【Fracturing & Destruction】点破小球——Unity3D中达到条件才触发的物体爆裂、炸裂、碎裂效果》( 点击打开链接),这里为了说明问题,我就不搞这么复杂,拉这么多无关重要的插件进来,降低代码的可读性。

同时赋予Attack_Button、Defend_Button和ExitButton,三个按钮点击事件分别为WarControl.cs的AttackButtonOnclick()、DefendButtonOnclick()和ExitButtonOnclick()则大功告成!


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Unity3D】回合制游戏 的相关文章

随机推荐

  • (Java) 算法题:2的N次方

    题目描述 原题链接 2的N次方 对于一个整数N 512 lt N lt 1024 计算2的N次方并在屏幕显示十进制结果 输入描述 输入一个整数N 512 lt N lt 1024 输出描述 2的N次方的十进制结果 输入例子1 512 输出例
  • 实现即时通讯的几种方式

    文章目录 1 短轮询 2 长轮询 3 SSE 4 WebSocket 总结 在 Web 应用程序中 实现即时通讯是一件常见的任务 为了实现即时通讯 我们需要使用一些特殊的技术和协议来建立一个实时连接 以便实时更新数据 在本文中 我们将介绍几
  • 本地编辑shopify主题的第一种方式

    先进入Shopify商店后台 新建应用程序 填写完无关紧要的信息后 把Theme templates and theme assets权限设置为读写访问权限并保存 然后复制密码 这表示可以通过这个密码对主题进行读写修改了 然后按照命令获取主
  • k8s-(五)最全的安装教程(使用kubeadm在Centos7上部署kubernetes1.18)以及安装异常问题记录

    k8s使用kubeadm进行安装步骤 使用kubeadm安装k8s会简单很多 一直想总结写一篇简单明了的安装教程 希望能有用 k8s在2020年初发布的第一个版本是1 18 0 目前最新版本是1 19 4 并且1 20的版本应该会在年底发布
  • Oracle PL/SQL中的循环处理(sql for循环)

    今年春节算是休了个长假 调整好心态 迎接新一年的挑战 今天来说下Oracle中的循环迭代处理 因为从自己的博客统计中看到 不少网友都搜索了关键字 SQL FOR循环 所以打算在这里说下个人的理解 PL SQL也和我们常用的编程语言一样 提供
  • 真香!用python做副业,月赚1W+,别被死工资拖累

    被压垮的打工人 你还好吗 房贷车贷 上老下小 日常开销 但你的收入有多少 所以你不敢生病 甚至不敢回家 就为了每个月那么点死工资 还得天天加班 然而忙忙忙 却变成了 穷忙族 成为了职场废人 其实很多人都想改变现状 想学点什么的 但就是不知从
  • c语言 字母消消乐,消消乐(C语言版)

    消消乐 游戏规则很简单 点击的位置颜色相连的区域抵消 实现思路 从点击位置开始深搜 递归 记录搜索的坐标并抵消 贴上关键代码 map数组保存每个点的颜色 state保存是否搜索过 判断当前点是否满足条件 并且未搜索过 int isValid
  • VS Code 快捷键(中英文对照版)

    标签 空格分隔 visual studio code 常用 General 按 Press 功能 Function Ctrl Shift P F1 显示命令面板 Show Command Palette Ctrl P 快速打开 Quick
  • 快速解决QQ自动下载腾讯视频播放器

    使用电脑QQ播放视频时 QQ总是会使用默认安装的腾讯视频播放器打开 可是他的这个播放器非常的卡 自己设置的默认不使用播放仍然不起作用 用geek观察了一下电脑 确实没发现腾讯视频 于是在播放视频的时候打开任务管理器 终于发现了腾讯视频播放器
  • 原理图以及vhdl设计一位全加器

    原理图设计以及VHDL设计 一位加法器 全加器原理 全加器真值 输出表达式 原理图设计法 VHDL设计法 代码如下 全加器是用门电路实现两个二进制数相加并求出和的组合线路 称为一位全加器 一位全加器可以处理低位进位 并输出本位加法进位 多个
  • 【目标检测】各种方法中比较难理解的地方

    1 评价指标mAP 全网最清楚的解释 强推 原文链接 http blog sina com cn s blog 9db078090102whzw html 理解的关键点在于每一次的precision和recall计算都是在top X的基础上
  • VMware虚拟机装win7教程

    VMware虚拟机装win7教程 前言 一 VMware虚拟机装win7 二 装Vmware Tools 1 初步装好win7后 要装Vmware Tools 2 搞不定的接着往下看 也是本人遇到的问题 总结 前言 昨晚想要在win10系统
  • 初识RecyclerView

    使用之前 implementation com android support recyclerview v7 26 1 0 添加v7的依赖 不然Recyclerview不给用 1 Xml布局 此处布局文件有两个 一个是整体的父布局文件 代
  • 利用Python实现几种常见排序算法

    一 排序算法概览 插入排序 直接插入排序 二分法插入排序 选择排序 直接选择排序 堆排序 交换排序 冒泡排序 快速排序 归并排序 二 代码实现 1 直接插入排序 最简单直接的一种方式 序列在排序中可分为左边已排序部分和右边未排序部分 每次从
  • 拷贝构造函数与深拷贝和浅拷贝

    拷贝构造函数是一种特殊的构造函数 函数的名称必须和类名称一致 它必须的一个参数是本类型的一个引用变量 作用就是用来复制对象 在使用这个对象的实例来初始化这个对象的一个新的实例 类中可以存在多个拷贝构造函数 拷贝构造函数的调用时机 当函数的参
  • Java中的代理(一)

    一 概念 代理 Proxy 是一种设计模式 提供了对目标对象另外的访问方式 即通过代理对象访问目标对象 这样做的好处是 可以在目标对象实现的基础上 增强额外的功能操作 即扩展目标对象的功能 重要的编程思想 不要随意去修改别人已经写好的代码或
  • Java中成员变量、局部变量、全局变量、静态变量存在位置及初始化

    根据定义变量位置的不同 可以将变量分为成员变量和局部变量 成员变量是 定义在一个类的变量 类中方法外 局部变量是定义在一个方法内的变量 方法中 成员变量分为 静态属性 随类的存在而存在 是在类加载的初始化 非静态属性 随实例的属性存在而存在
  • vsnprintf用法解析

    int vsnprintf char s size t n const char format va list arg 描述 将格式化数据从可变参数列表写入大小缓冲区 如果在printf上使用格式 则使用相同的文本组成字符串 但使用由arg
  • 深度解析转置卷积,理解转置卷积的原理

    参考文章 转置卷积 Transposed Convolution 太阳花的小绿豆的博客 CSDN博客 转置卷积 抽丝剥茧 带你理解转置卷积 反卷积 史丹利复合田的博客 CSDN博客 逆卷积和转置卷积 参考视频 转置卷积 transposed
  • 【Unity3D】回合制游戏

    回合制游戏一直在游戏史 至少是在中国的游戏历史上扮演很重要的角色 从仙剑到梦幻 这类游戏深受玩家喜爱 那么在Unity3D中怎么实现呢 下面用一个比较简单Unity3D的一对一回合制游戏来说明这个问题 其实也不难 关键是理清各个处理关系 如