牧师与魔鬼 -- version2 动作分离

2023-11-01

一、基本操作演练

1、下载 Fantasy Skybox FREE, 构建自己的游戏场景

  • 下载天空盒 Fantasy Skybox FREE,它包含了一些天空贴图在这里插入图片描述

    将下载的天空盒文件导入
    在这里插入图片描述

  • 创建 Material(Assets 上下文菜单 -> create -> Material) 起名 mysky,在 Inspector 视图中选择 Shader -> Skybox -> 6Sided,并且将刚刚下载的天空盒图片依次加入到右面六张图片的空缺处,结果如图:
    在这里插入图片描述

  • 运行,就能够看到刚刚做好的天空盒
    在这里插入图片描述

  • 加上地形以及花草树木
    在这里插入图片描述

2、写一个简单的总结,总结游戏对象的使用

Unity 游戏对象主要涉及三种类

  • GameObject: Unity 场景中所有实体的基类
  • Component: 能附加到游戏对象的部件的基类
  • Component 的各种子类。包括空间与变换部件 Transform、各种 渲染部件Reander ,脚本部件,MonoBehaviour 的子类等等。

游戏对象的使用

  • 所有游戏对象都有Active属性,Name属性,Tag属性等。每个 GameObject 还包含一个 transform 组件,我们可以通过这个组件来使游戏对象改变位置,旋转和缩放。
  • 通过改变物体的 material ,我们可以赋予它们不同的材质或者说外观。
  • 将 GameObject 做成预制是一个重要的游戏对象的使用方法,可以大大减少重复劳动的工作。将 游戏对象 拖动并放置在 Assets 面板之上,就生成了一个预制件
  • 对 GameObject 来说最常用的添加功能的方法是添加组件(脚本),这样通过组合的方式既拓展了对象的功*能,还将组件与对象耦合度大大降低。

之前使用过的游戏对象

  • 3D 物体:Cube、Sphere、Capsule、Sylinder、Plane、Quad,由三角形网格构建的物体:如Terrain。

  • Empty (不显示却是最常用对象之一):作为子对象的容器或创建一个新的对象空间。

  • Camera 摄像机:观察游戏世界的窗口。
    Projection属性包括正交视图与透视视图。Viewport Rect:属性是控制摄像机呈现结果对应到屏幕上的位置以及大小。屏幕坐标系:左下角是(0, 0),右上角是(1, 1)。Depth属性是当多个摄像机同时存在时,这个参数决定了呈现的先后顺序,值越大越靠后呈现。

  • light 光源
    灯光类型(type)包括平行光(类似太阳光),聚光灯(spot),点光源(point),区域光(area,仅烘培用)

  • audio 声音
    将声音素材拖入摄像机就可以成为背景音乐。可以设置是否重复,音量等属性

  • skyboxes 天空盒
    天空是一个球状贴图,通常用 6 面贴图表示。
    使用方法有两步,第一为在摄像机添加天空组件。Component 加入skybox组件,第二为直接拖入天空材料(Material)。


二、编程实践

1、牧师与魔鬼 动作分离版

**游戏视频github 地址戳这里

在这里插入图片描述


面向对象的游戏编程

如果感觉到场记(控制器)管的事太多,不仅要处理用户交互事件,还要游戏对象加载、游戏规则实现、运动实现等等,而显得非常臃肿。一个最直观的想法就是让更多的人(角色)来管理不同方面的工作。显然,这就是面向对象基于职责的思考,例如:用专用的对象来管理运动,专用的对象管理播放视频,专用的对象管理规则。就和踢足球一样,自己踢5人场,一个裁判就够了,如果是国际比赛,就需要主裁判、边裁判、电子裁判等角色通过消息协同来完成更为复杂的工作。


动作管理器的设计思想

为了用一组简单动作组合成复杂的动作,我们采用 cocos2d 的方案建立与 CCAtion 类似的类。设计思路如下:

  • 设计一个抽象类作为游戏动作的基类
  • 设计一个动作管理器类管理一组游戏动作的实现类
  • 通过回调,实现动作完成时的通知

动作管理器的设计类图

在这里插入图片描述

动作管理器的代码与设计解读
  • SSAction (动作基类)

    动作基类继承于ScriptableObject, 是不需要绑定 GameObject 对象的可编程基类。这些对象受 Unity 引擎场景管理。
    动作基类中申明了 StartUpdate 的虚函数,通过重写可以实现多态,这样继承者可以明确使用 Start 和 Update 编程游戏对象行为。
    为了在完成动作后告知管理者,同时避免与动作管理者直接依赖,动作基类中定义了接口 ISSACtionCallback 实现消息通知,避免与动作管理者直接依赖。

  public class SSAction : ScriptableObject {
    public bool enable = true;		//允许动作发生
    public bool destroy = false;	//销毁动作
    public GameObject gameobject { get; set; }  //动作对象
    public Transform transform { get; set; }	//动作对象的 transform
    public ISSActionCallback callback { get; set; }	//动作管理者//确保使用者不能够随意的创建动作
    protected SSAction() { }//子类需要对 Start 和 Update 进行重写
    public virtual void Start() {
        throw new System.NotImplementedException();
    }public virtual void Update() {
        throw new System.NotImplementedException();
    }
}

  • CCMoveToAction(简单动作)

    实现将一个物体移动的目标位置,并通知 callback 对象任务已完成的功能。
    一个易错点在于,需要加上对 enable 的判断来决定是否需要移动物体,不能够让函数始终处于触发状态。因为当人物在跟随船移动的时候是不符合处于目标点的状态的,这样运行后会发现人物处于一个固定点,没有跟着船移动。

public class CCMoveToAction : SSAction {
    public Vector3 target;	//移动目标位置
    public float speed;		//移动速度
   
    //函数 GetSSAction 返回一个设定好目标和移动速度的 action 对象
    public static CCMoveToAction GetSSAction(Vector3 target, float speed) {
    	CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
    	action.target = target;
    	action.speed = speed;
	return action; 
    }//每次 Update 时物体进行少量的移动,为了平滑的移动到目标位置,需要由继承了Monobehavior类的对象来不断调用CCMoveToAction实例的Update函数
    public override void Update() {
        //一个易错点在于,需要加上对enable的判断来决定是否需要移动物体,不能够让函数始终处于触发状态。因为当人物在跟随船移动的时候是不符合处于目标点的状态的,这样运行后会发现人物没有跟着船移动。
        if (enable) {
            this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed);
            if (this.transform.position == target) {
                this.destroy = true;
                this.enable = false;
                this.callback.SSActionEvent(this);
            }
        }
    }public override void Start() {
        Update();
    }
}

  • SequenceAction (顺序动作组合类)

    顺序动作是标准的组合设计模式,被组合的对象和组合对象属于同一种类型。通过组合模式,我们能实现几乎满足所有越位需要、非常复杂的动作管理。
    在设计中需要让动作组合继承抽象动作,能够被进一步组合;实现回调接受,能接收被组合动作的事件;
    创建一个动作顺序执行序列,-1 表示无限循环,start 开始动作。
    Start 执行动作前,为每个动作注入当前动作游戏对象,并将自己作为动作事件的接收者
    Update 方法执行执行当前动作
    SSActionEvent 收到当前动作执行完成,推下一个动作,如果完成一次循环,减次数。如完成,通知该动作的管理者
    OnDestory 如果自己被注销,应该释放自己管理的动作。

public class SequenceAction : SSAction, ISSActionCallback {
    public List<SSAction> sequence;    
    public int repeat = -1;    //循环次数、-1表示无限循环       
    public int start = 0;      //进行到哪个动作的标识
    public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence) {
    SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
    action.repeat = repeat;
    action.sequence = sequence;
    action.start = start;
    return action;
}public override void Update() {
    if (sequence.Count == 0) return;
    if (start < sequence.Count) {
        sequence[start].Update();     
    }
}public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed) {
        source.destroy = false;          
        this.start++;
        if (this.start >= sequence.Count) {
            this.start = 0;
            if (repeat > 0) repeat--;
            if (repeat == 0) {
                this.destroy = true;               
                this.callback.SSActionEvent(this); 
            }
        }
    }public override void Start() {
        foreach (SSAction action in sequence) {
            action.gameobject = this.gameobject;
            action.transform = this.transform;
            action.callback = this;                
            action.Start();
        }
    }void OnDestroy() {
        //todo
    }
}

  • ISSActionCallback(动作事件接口)
public enum SSActionEventType : int { Started, Completed }
public interface ISSActionCallback {
    void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed);
}

  • SSActionManager(动作管理基类)

    管理 SequenceAction 和 SSAction,可以给它们传递游戏对象,让游戏对象做动作或是一连串的动作,控制动作的切换。
    SSActionManager 继承了 ISSActionCallback 接口,通过这个接口,当动作做完或是连续的动作做完时候会告诉 SSActionManager,然后 SSActionManager 去决定如何执行下一个动作。

public class SSActionManager : MonoBehaviour, ISSActionCallback {   
    //RunAction 函数为 Action 设定游戏对象以及动作结束后的通知者
    public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback callback) {
        action.gameobject = gameobject;
        action.transform = gameobject.transform;
        action.callback = callback;
        action.Start();
    }public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed) {}}

  • CCActionManager (动作管理)

    在具体的动作管理中依照游戏要求需要实现两个动作,移动船只以及移动人物。
    船只只包含平移的动作,所以使用简单的 CCMoveToAction 动作来执行;人物的动作由跳起、跃下,以及水平移动组成,所以需要使用顺序动作来控制。

public class CCActionManager : SSActionManager {
    private CCMoveToAction moveBoat;	//移动船只
    private SequenceAction moveCharacter;public ISceneController controller;	//移动人物public void Start() {
    controller = SSDirector.getInstance().currentSceneController;
    controller.actionManager = this;
}//动作管理器继承了 MonoBehaviour,而动作本身不具有在每个 tick更新状态的功能,所以管理器在每个自身 Update 被调用的时候要检查所管理的动作的状态,并调用相应的Update函数。
public void Update() {
    if (moveBoat) moveBoat.Update();
    if (moveCharacter) moveCharacter.Update();
}public void MoveBoat(GameObject boat, Vector3 target, float speed) {
    //获取以特定速度移动到 target 的动作对象
    moveBoat = CCMoveToAction.GetSSAction(target, speed);
    //将动作对象与船只对象绑定,让船只在动作的指挥下移动
    RunAction(boat, moveBoat, this);
}//使用顺序动作来控制人物的动作:跳起、跃下,以及水平移动
public void MoveCharacter(GameObject character, Vector3 target, float speed) {
    Vector3 forward, up, down;
    
    //设置跳起的水平距离,根据人物所处位置决定差值为正数或负数
    float jumpDistance = target.x < 0 ? -8 : 8;
   
    List<SSAction> sequence;
    
    //人物跃下下方的船只
    if (character.transform.position.y > target.y) {
        forward = target;
        forward.x += jumpDistance;
        forward.y = character.transform.position.y;
        down = target;
        CCMoveToAction moveForward = CCMoveToAction.GetSSAction(forward, speed);
        CCMoveToAction jump = CCMoveToAction.GetSSAction(down, speed);
        //顺序动作由前进、跃下组成
        sequence = new List<SSAction> { moveForward, jump };
    }
    //人物跳上上方的河岸
    else {
        forward = target;
        up = character.transform.position;
        up.x += jumpDistance;
        up.y = target.y;
        CCMoveToAction moveForward = CCMoveToAction.GetSSAction(forward, speed);
        CCMoveToAction jump = CCMoveToAction.GetSSAction(up, speed);
        //顺序动作由跳起、前进组成
        sequence = new List<SSAction> { jump, moveForward };
    }//设定动作的执行次数为 1
    moveCharacter = SequenceAction.GetSSAcition(1, 0, sequence);
    RunAction(character, moveCharacter, this);
}}

裁判类的设计实现
  • 通知 ISceneController 的接口设计
public enum GameStatus : int { win, lose, playing }
public interface ISSJudgeCallback {
    void SSJudgeEvent(ISceneController source, GameStatus status = GameStatus.playing);
}

  • 裁判类 Judge 的设计实现
public class Judge :MonoBehaviour, ISSJudgeCallback {
    CharacterModel[] roles = new CharacterModel[6];
    ISceneController callback;
    BoatModel boat;int countOfDevil_1, countOfDevil_2, countOfPriest_1, countOfPriest_2;public void setJudge(CharacterModel[] characters, BoatModel boat, ISceneController source) {
    for(int i = 0; i < 6; i++) {
        this.roles[i] = characters[i];	//裁判需要监视的对象
    }
    this.boat = boat;
    callback = source;
}public void SSJudgeEvent(ISceneController source, GameStatus status = GameStatus.playing) {
    source.status = status;	//告知 ISceneController 此时的游戏状态
}public void Update() {
    if (!boat.isSailing()) return;  //在船行驶的时候才对游戏状态进行判断
    if (callback.getGameStatus() == GameStatus.playing) {
        countOfDevil_1 = countOfDevil_2 = countOfPriest_1 = countOfPriest_2 = 0;
        //对两岸的游戏人物分别计数,查看是否符合游戏结束的条件
        for (int i = 0; i < 6; i++) {
            if (roles[i].getCoastName() == "leftCoast") {
                if (roles[i].getType() == "priest") countOfPriest_1++;
                else countOfDevil_1++;
            }
            else {
                if (roles[i].getType() == "priest") countOfPriest_2++;
                else countOfDevil_2++;
            }
        }
        if (countOfDevil_1 > countOfPriest_1 && countOfPriest_1 > 0 || countOfDevil_2 > countOfPriest_2 && countOfPriest_2 > 0) {
            SSJudgeEvent(callback, GameStatus.lose);
        }
        else if (countOfPriest_1 + countOfDevil_1 >= 6) {
            SSJudgeEvent(callback, GameStatus.win);
        }
    }
}}

ISceneController 的重新设计

由于本次我们实现了将动作控制从 ISceneController 的工作中分离,所以需要对原先 ISceneController 直接控制船/人物模型的动作部分进行修改。
首先需要在 ISceneController 中添加成员 public CCActionManager actionManager,这样就可以通过对actionManager 的操控间接控制动作发生,并且不需要了解太多细节。

  • moveBoat
public void moveBoat() {
        if (boat.isEmpty() || boat.isSailing()) return;    //通过 actionManager 间接控制动作发生
    actionManager.MoveBoat(boat.getBoat(), boat.getAndSetAnotherPort(), 0.50f);
    for (int i = 0; i < 6; i++) {
        if (characters[i].isOnBoat()) {
            characters[i].setCoastName(boat.getCoastName());
        }
    }
}
  • moveCharacter
public void moveCharacter(CharacterModel character) {
        if (boat.isSailing()) return;
        if (character.isOnBoat()) {
            //character.setCoastName(boat.getCoastName());
            character.leaveBoat();
            boat.getOff(character.getSeatIndex());        
            Vector3 newPos;
        int index;
        if (character.getCoastName() == "leftCoast") {
            index = leftCoast.getVacantIndex();
            newPos = character.getCoastPosition(index);
            newPos.x = -newPos.x;
        }
        else {
            index = rightCoast.getVacantIndex();
            newPos = character.getCoastPosition(index);
        }
        character.setPosIndexOnCoast(index);
        actionManager.MoveCharacter(character.getCharacter(), newPos, 0.50f);
    }
    else {
        if (boat.isFull() || boat.getCoastName() != character.getCoastName()) return;
        int index = character.getPosIndexOnCoast();       
        if (character.getCoastName() == "leftCoast") leftCoast.getOff(character.getPosIndexOnCoast());
        else rightCoast.getOff(character.getPosIndexOnCoast());
        int seat = boat.getVacantIndex();
        character.setSeat(seat);
​
        actionManager.MoveCharacter(character.getCharacter(),boat.getBoat().transform.position + 
            character.getBoatPosition(seat), 0.50f);
        character.board(boat.getBoat().transform);
    }
}

三、材料与渲染联系

从 Unity 5 开始,使用新的 Standard Shader 作为自然场景的渲染。

阅读官方 Standard Shader 手册 。
选择合适内容,如 Albedo Color and Transparency,寻找合适素材,用博客展示相关效果的呈现

Albedo参数控制表面的基础色调
在这里插入图片描述
官网上由Albedo参数控制的物体颜色例子:
在这里插入图片描述

下面在Unity上做一个简单的练习。

  • 首先通过 Assets -> create material
  • 在新建的 material 的 inspector 面板上修改其 Albedo 值
  • 新建一个 sphere 来展示刚刚做好的 material
  • 将材料拖放到 sphere 上去
  • 按照以上步骤制作五个不同 Albedo 的sphere

结果如下图,可以看到 Albedo 成功改变了物体表面颜色
在这里插入图片描述

A另外,lbedo 颜色的 alpha 通道控制材质的透明度程度。需要注意的是,这只对材质中的Rendering Mode为透明模式(Transparent、Fade)有效,而Opaque模式没有效果。
为上面的sphere添加透明度后效果如下:
在这里插入图片描述

官网上给出的 transparent 的使用样例很棒。镜头可以透过破损的窗口看到建筑物内。玻璃裂开的缝隙是完全透明的,而其余的部分是半透明的。
在这里插入图片描述

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

牧师与魔鬼 -- version2 动作分离 的相关文章

随机推荐

  • JAVA实现图片质量压缩和加水印

    这个世界没有什么好畏惧的 反正我们只来一次 文章目录 前言 编写代码 1 编写工具类 2 编写接口 3 测试接口 总结 前言 主要实现了两个功能 加水印 质量压缩 编写代码 1 编写工具类 ImageUtil代码如下 package com
  • ceph中的Pools、PGs和OSDs介绍(tmp)

    2019独角兽企业重金招聘Python工程师标准 gt gt gt How are Placement Groups used A placement group PG aggregates objects within a pool be
  • Python-栈结构

    栈 stack 又名堆栈 它是一种运算受限的线性表 栈只能在一端进行插入和删除操作 它按照先进后出 FILO 的原则存储数据 先进入的数据被压入栈底 最后的数据在栈顶 栈也可以看成是 FILO 的队列 class Stack object
  • String类常用方法

    红色为常用的方法 1 和长度有关的方法 得到一个字符串的字符长度 String s abc s length 2 和数组有关的方法 返回类型 方法名 作用 byte getBytes 将一个字符串转换成字节数组 char toCharArr
  • mysql对表中列的操作_mysql对表基本操作

    一 对表的操作 1 添加新的字段 alter table 表名 add name varchar 20 2 删除表中已有的字段 alter table 表名 drop name 3 修改表中已有的字段 alter table 表名 chan
  • js 计算两个日期之间的相差的天数

    将两个日期都转换为毫秒相减后 将减下来的毫秒数转换为天数 就可以得到两个日期之间相差的天数了 接受的日期格式为 2023 1 31 2023 2 28 的日期字符串 const getDaysApart date val date vals
  • ubuntu下jmxtrans 安装

    JAVA 监控内容收集之 Jmxtrans 它是一个为应用程序 设备 系统等管理功能的框架 通常可以用来监控和管理Java应用系统 1 拷贝jmxtrans至LS1上 scp jmxtrans 251 deb LS1 2 安装jmxtran
  • Google Chrome在Windows7安装离线版

    前言 今天因为旧版chrome老是要报更新 所以安装了个新版 因为被墙原因 许多网友会遇到一些安装chrome的问题 所以今天分享一下安装教程 安装chrome 1 前往chrome官网 可以看到链接地址是http www google c
  • 如何构造测试数据

    前言 我这里只是专注于生成CSV等测试数据文件 每次构造测试数据的时候就很头疼 之前自己简单造个两三行还行 造多了就有些费脑细胞了 抽出些时间来专门找一下有没有相应工具 小数据量测试数据 小数据量测试数据使用在线的网站就行 10W以内的数据
  • 【Python】使用Python根据BV号爬取对应B站视频下的所有评论(包括评论下的回复)

    Python 使用Python根据BV号爬取对应B站视频下的所有评论 包括评论下的回复 本文写于2020 4 27 当你阅读到本文的时候如果因为下列原因导致本文代码无法正常工作 本人概不负责 B站的页面和API接口的变动 B站为页面和API
  • 操作系统笔记(手写)

    前言 这学期开始学习计算机网络 操作系统和Java程序设计 这些课的重要性不言而喻 对于我这种纯粹的小白来说 压力真得很大 自己水平有限 领悟能力较差 学习接受能力很慢 不知道怎样才能真真的学懂 学会这些东西 所以就先跟着学校安排的网课和配
  • 常见的数据结构与算法

    文章目录 前言 一 常见的数据结构 1 数组 2 链表 3 栈 4 队列 5 树 二 排序 1 基本的排序算法 2 常考的排序算法 3 其他排序算法 三 递归与回溯 1 递归 2 回溯 四 深度与广度优先搜索 1 深度优先搜索 2 广度优先
  • 伴随矩阵介绍及C++实现

    在线性代数中 一个方形矩阵的伴随矩阵是一个类似于逆矩阵的概念 如果矩阵可逆 那么它的逆矩阵和它的伴随矩阵之间只差一个系数 然而 伴随矩阵对不可逆的矩阵也有定义 并且不需要用到除法 设R是一个交换环 在抽象代数之分支环论中 一个交换环 com
  • 【vue】vue子孙组件传值(多级嵌套)attrs listeners

    如果vue开发遇到多层嵌套 子孙组件之间传值 可以使用 attrs listeners传值 示例如下 孙子组件
  • 装上这10个插件,PyCharm才是无敌的存在

    pycharm是一款强大的python集成开发环境 带有一整套python开发工具 今天就给大家介绍几款非常好用的插件 首先插件的下载方法 进入File gt Settings gt Plugins 根据需要搜索插件名称 记得是在Marke
  • db是哪个城市的缩写_全国所有城市拼音及缩写

    北京 BEIJING BJ 上海 SHANGHAI SH 天津 TIANJIN TJ 重庆 CHONGQING ZQ 阿克苏 AKESU AKS 安宁 ANNING AN 安庆 ANQING AQ 鞍山 ANSHAN AS 安顺 ANSHU
  • 分享一款开源堡垒机-jumpserver

    JumpServer是由FIT2CLOUD 飞致远 公司旗下一款开源的堡垒机 这款也是全球首款开源的堡垒机 使用 GNU GPL v2 0 开源协议 是符合 4A 规范的运维安全审计系统 使用 Python 开发 遵循 Web 2 0 规范
  • java basefont_itext 文本域 字体样式设置

    使用acroFields setFieldProperty nameField textfont baseFont null 的方式不能加粗 因为第三个参数必须是BaseFont类型 不能是Font类型 可以使用下面的方式加粗 BaseFo
  • 判断环形链表是否有环??返回环形链表的入口点!!

    上次笔者写了一篇大概有7个题的链表相关的题目 解析 感觉还不错 感兴趣的各位老铁 可以点一下链接进行欣赏 做几个与链表相关的题吧 https blog csdn net weixin 64308540 article details 128
  • 牧师与魔鬼 -- version2 动作分离

    目录 一 基本操作演练 1 下载 Fantasy Skybox FREE 构建自己的游戏场景 2 写一个简单的总结 总结游戏对象的使用 二 编程实践 1 牧师与魔鬼 动作分离版 面向对象的游戏编程 动作管理器的设计思想 动作管理器的设计类图