`

Clyde学习笔记二(CoordIntMap)

阅读更多

CoordIntMap是一个基类为Map数据结构,是存储游戏地图场景数据的基础数据结构,在应用中一共涉及到3个类,第一个自然是CoordIntMap,另外还有Coord和Cell,Cell定义在CoordIntMap中,是一个内部类。

 

Coord用一个int来表示和存储一个2D的坐标,在存储和表示之前分别需要encode和decode。

    /**
     * Encodes the supplied coordinates (presumed to be in [-32768, +32767]) into a single
     * integer.
     */
    public static int encode (int x, int y)
    {
        return (x << 16) | (y & 0xFFFF);
    }

    /**
     * Extracts the x component from the specified encoded coordinate pair.
     */
    public static int decodeX (int pair)
    {
        return pair >> 16;
    }

    /**
     * Extracts the y component from the specified encoded coordinate pair.
     */
    public static int decodeY (int pair)
    {
        return (pair << 16) >> 16;
    }

 一个int类型有4个字节,前2个字节表示x坐标,后2个字节表示y坐标。取值范围从-32768一直到 +32767。

 

对于CoordIntMap,我们可以从它的定义中看到下面这2个接口,x、y是一个2D的坐标,显然CoordIntMap是一个以x、y2D坐标为key来存取对应的int值的这样一个map结构。

    /**
     * Retrieves the value at the specified coordinates.
     */
    public int get (int x, int y)
    {
       ...
    }

    /**
     * Sets the value at the specified coordinates.
     *
     * @return the previously stored value.
     */
    public int put (int x, int y, int value)
    {
        ....
    }

 但是它的内部存储是通过下面这个map来实现的

protected HashMap<Coord, Cell> _cells = new HashMap<Coord, Cell>();

 

 

可以看到,对外的接口与内部的存储方式并不一致,那么它是如何来转换这两者之间的差异的呢?

我们先来看一下CoordIntMap的构造函数:

    /**
     * Creates a new coord int map.
     *
     * @param granularity the size of the top-level cells, expressed as a power of two (e.g.,
     * 3 for a cell size of 8 by 8).
     * @param empty the value indicating the absence of an entry.
     */
    public CoordIntMap (int granularity, int empty)
    {
        _granularity = granularity;
        _empty = empty;
        initTransientFields();
    }

 

我们注意一下granularity这个参数,它的中文意思是粒度,在这里是2的幂指数,用来指定Cell的矩阵大小,granularity值越大,Cell内包含的元素越多,粒度越小。


Cell的构造函数:

        /**
         * Creates a new cell.
         */
        public Cell ()
        {
            _values = new int[1 << _granularity << _granularity];
            Arrays.fill(_values, _empty);
        }

 1 << _granularity << _granularity这个表达式的数学含义是4的_granularity次方。默认_granularity=3,初始化一个8x8的数组,并且初始化成空值。

 

Cell内部是用一个一维数组来表示和存储数据的,也就是说最终的数据都是存储在下面这个数组内的。

        /** The values in the cell. */
        protected int[] _values;
 

那么,CoordIntMap是如何通过x、y这组2D坐标映射到_values数组中的值呢?我们来看一下get方法的具体实现:

 

    /**
     * Retrieves the value at the specified coordinates.
     */
    public int get (int x, int y)
    {
        Cell cell = getCell(x, y);
        return (cell == null) ? _empty : cell.get(x & _mask, y & _mask);
    }

    /**
     * Returns the cell corresponding to the specified coordinates.
     */
    protected Cell getCell (int x, int y)
    {
        _coord.set(x >> _granularity, y >> _granularity);
        return _cells.get(_coord);
    }
 

 

首先,x、y被映射成一个Coord,然后根据这个Coord的值在内部map中找到cell,最后再从cell中得到值。这里最重要的就是对x、y的映射算法,这也是整个数据结构的核心。

 

x >> _granularity, y >> _granularity翻译成数学语言是x除以2的_granularity次方,y除以2的_granularity次方。还记得前面提到过的对Cell构造函数的解释吗?每一个Cell都是一个2的_granularity次方 x(乘) 2的_granularity次方的结构,那么这里对x、y的映射是用来计算x、y所对应的value所在的Cell的坐标

 

在得到Cell后如何再取得最终的值呢?在理解cell.get(x & _mask, y & _mask)这个表达式之前我们先来看下CoordIntMap构造函数中的调用的initTransientFields()这个方法。

 

    /**
     * Initializes the transient fields.
     */
    protected void initTransientFields ()
    {
        _mask = (1 << _granularity) - 1;
    }

 这个方法用来计算mask的值,我们刚才已经看到,这个mask在我们从Cell中取值会被用到。那么,在这里的这个表达式(1 << _granularity) - 1 我们已经比较熟悉了,数学含义就是2的_granularity次方-1。如果:

 

_granularity = 2, 那么1 << _granularity) - 1 = 3, 2进制值为 11
_granularity = 3, 那么1 << _granularity) - 1 = 7, 2进制值为 111
...
 

我们回过头再来看表达式cell.get(x & _mask, y & _mask),分别用mask对x、y做和的位运算,其数学含义等同于分别用2的_granularity次方除x、y取余,这样其实得到了x、y映射到Cell中的坐标。

 

为了更好的理解,特意编辑了2个实例示意图,相信大家都能理解。

 


 

既然CoordIntMap它是一个Map,那么它必然要提供一个接口供调用者来遍历每一个entry。CoordIntMap对外提供了这样的一个方法,它用一个Iterator来遍历内部的数据。

 

    /**
     * Returns a set view of the map entries.
     */
    public Set<CoordIntEntry> coordIntEntrySet ()
    {
        return new AbstractSet<CoordIntEntry>() {
            public Iterator<CoordIntEntry> iterator () {
                return new Iterator<CoordIntEntry>() {
                	...
                };
            }
            public int size () {
                return _size;
            }
        };
    }

 

Iterator的定义:

 

public Iterator<CoordIntEntry> iterator () {
    return new Iterator<CoordIntEntry>() {
				...
        public CoordIntEntry next () {
            checkConcurrentModification();
            if (_centry == null) {
                _centry = _cit.next();
            }
            while (true) {
                int[] values = _centry.getValue().getValues();
                for (; _idx < values.length; _idx++) {
                    int value = values[_idx];
                    if (value != _empty) {
                        Coord coord = _centry.getKey();
                        _dummy.getKey().set(
                            (coord.x << _granularity) | (_idx & _mask),
                            (coord.y << _granularity) | (_idx >> _granularity));
                        _dummy._values = values;
                        _dummy._idx = _idx;
                        _idx++;
                        _count++;
                        return _dummy;
                    }
                }
                _centry = _cit.next();
                _idx = 0;
            }
        }
				...
        protected Iterator<Entry<Coord, Cell>> _cit = _cells.entrySet().iterator();
        protected Entry<Coord, Cell> _centry;
        protected int _idx;
        protected int _count;
        protected int _omodcount = _modcount;
        protected CoordIntEntry _dummy = new CoordIntEntry();
    };

 

 这里只截取了next方法,展示了跟前相反的操作过程,即通过内部的存储数据来取得最初的x、y的值。这里最关键的两个表达式:

(coord.x << _granularity) | (_idx & _mask)

(coord.y << _granularity) | (_idx >> _granularity)

这两个表达式分别取得x、y的值然后存储为dummy中的key,这里的key虽然也是Coord类型,但含义跟我们前边看到的那个Coord完全不同,存储的直接是x、y的值

 

        /** The coordinate key. */
        protected Coord _key = new Coord();

 

 如果我们跑一下下面的这段代码

 

		CoordIntMap map = new CoordIntMap();
		map.put(5, 7, 112);
		map.put(1, 3, 57);
		map.put(8, 6, 33);
		Set<CoordIntEntry> set = map.coordIntEntrySet();
		for (CoordIntEntry entry : set) {
			System.out.println("key: " + entry.getKey());
			System.out.println("value: " + entry.getValue());
		}

 可以预期得到的输出为key: [1, 3]

 

value: 57
key: [5, 7]
value: 112
key: [8, 6]
value: 33

 

备注:本文中涉及到的位运算及其含义

1 << _granularity << _granularity

4的_granularity次方

 

x >> _granularity, y >> _granularity

x除以2的_granularity次方,y除以2的_granularity次方

 

 

(1 << _granularity) - 1

2的_granularity次方-1

 

 

x & _mask, y & _mask

2的_granularity次方除x、y取余

 

(coord.x << _granularity) | (_idx & _mask)

coord.x乘以2的_granularity次方加上_idx除以_mask取余

 

(coord.y << _granularity) | (_idx >> _granularity)

coord.y乘以2的_granularity次方加上_idx除以2的_granularity次方取整

 

 

后记:

CoordIntMap的实现中大量使用了位运算,这样做的目的主要是为了提高效率,毕竟这是一个基础性的数据结构,特别是在处理地图场景等大数据量的时候,效率还是比较关键的。其实类似的用法我们在JDK中也能看到,比如在HashMap中,通过hashcode定位数组index的取余方法JDK中是这样实现的:

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
 

在这里,当length为2的幂时,h & (length-1)等价于h % length,但是前者的效率要高于后者。怎么样,这里的length - 1和CoordIntMap中的mask是否有异曲同工之妙呢?

  • 大小: 14.4 KB
分享到:
评论

相关推荐

    无参数Zig Zag,A-la Clyde Lee 模式 - MetaTrader 5脚本.zip

    无参数Zig Zag,基于"a-la Clyde Lee 模式"。

    Clyde:Clyde 是一款兼容 Arduino 的开源灯,具有很多个性,您可以适应、玩耍并真正称其为您自己的灯

    克莱德Clyde 是一盏个性十足的灯,您可以适应、玩耍并真正称其为您自己的灯。 Clyde 是开源的并且与 Arduino 兼容。 您可以通过在主控制器板上插入个性模块来改变克莱德对环境的React。 您还可以通过使用标准 ...

    clyde-开源

    通过HTTP维护的轻量级代码,文档或文件保留应用程序。 用户可以创建新的库,子类别和上/下加载文件。 易于使用是其最大的优势。

    clyde:对话语言和游戏工具

    克莱德-对话语言克莱德(Clyde)是一种用于编写游戏对话的语言。 它支持通过变量和事件进行分支对话,翻译和与游戏的接口。 它在很大程度上受到启发,但它着重于对话而不是叙述。 您可以与在线编辑器一起玩。 这是一...

    clyde

    版 Jekyll的产品文档模板。 浏览。 开始使用此可配置主题记录您的产品,应用程序,服务或网站。 该版本由的Cloud CMS 制作。 在找到更多模板,主题和分步的Jekyll教程。 特征 ... 获得一个工作流,以查看您站点的输出...

    frc333-2018-clyde:FRC团队333-2018克莱德代码

    【标题】"frc333-2018-clyde:FRC团队333-2018克莱德代码"所代表的是一个与FIRST Robotics Competition ...这个项目为学习和研究FRC机器人编程提供了一个实际的案例,有助于理解在竞赛中如何使用Java进行机器人控制。

    clyde:用于制作网络3D游戏的Java软件包的集合

    导出-版本弹性序列化为XML和二进制数据格式 expr-表达式评估和符号绑定 数学-基本的2D和3D数学课程 openal-将Nenya的OpenAL支持与OpenGL场景处理联系在一起 opengl-画布和基于显示的OpenGL应用程序的基类 opengl....

    软件测试-实验7.docx

    在第二关中,针对Game类的move方法进行了测试。当move方法被注释掉后,冒烟测试失败,因为move方法是游戏中角色移动的关键功能,注释会导致实际游戏逻辑无法执行,从而引发断言失败。这强调了测试用例应覆盖所有核心...

    clyde-kun:我的GitHub个人资料的配置文件

    当前使用和学习 有兴趣学习 | | | | | | | --- | --- | --- | --- | --- | --- | --- || 工具 :hammer_and_wrench: 当前使用和学习 有兴趣学习 其他账户 :closed_mailbox_with_raised_flag: 您可以在这些帐户上找到...

    Clyde Project:可持续能源概念、方法、模型和应用-开源

    克莱德项目将开发一个电子上下文模型,用于可持续能源概念的知识建模,以及关于可持续、可再生能源系统的实用方法、模型和应用的知识展示。

    Printed Circuits Handbook

    印刷电路板手册第六版 Part 1 Lead-Free Legislation Chapter 1. Legislation and Impact on Printed Circuits 1.3 1.1 Legislation Overview / 1.3 1.2 Waste Electrical and Electronic Equipment (WEEE) / 1.3 ...

    Mrs-Pac-Man:仅使用Javascript,CSS和HTML重新制作经典的《吃豆人》经典游戏

    玩家在迷宫中导航吃豆人,以收集点,同时避开五颜六色的幽灵(眨眼,小指,墨黑和克莱德)。 如果他们成功收集了所有圆点,则玩家将赢得一个等级。...Pinky和Clyde AI逻辑的代码片段: function ghos

    unity复刻 Pac-man 吃豆人游戏

    1980年pacman发布,仅有2.47MB游戏中红色的幽灵...4.黄色的幽灵叫做clyde,代表词是“随意”,它的行动路线完全随机。利用这种机制制造出假AI欺骗玩家,即使在现在也是也难以想象。复刻中使用随机路线巡逻。技术力不够

    传媒行业深度报告:AI系列:AI赋能数字人变数智人,丰富应用场景加速落地

    Discord公司在2023年3月应用OpenAI更新其聊天机器人Clyde,以实时回应用户问题,并拓展对话。 AI赋能数字人变数智人是当前传媒行业深度报告的主要内容。AI技术的应用可以赋能数字人,使其变数智人,创造更多的应用...

    The Book of Dads

    - **育儿的挑战与乐趣**:无论是夜间的守护还是日常生活的琐碎,父亲们都在不断学习如何更好地陪伴孩子成长。 - **成长的反思**:许多文章都提到了时间的流逝以及对未来的期待,反映出父亲对于孩子成长过程中所经历...

    Printed circuits handbook(6th)

    #### 二、手册覆盖的核心内容 - **PCB设计基础**:介绍PCB的基本概念、设计原则、材料选择等方面的知识。 - **制造工艺**:深入讲解PCB的制造流程,包括光刻、蚀刻、钻孔等关键步骤。 - **可靠性测试**:提供有关...

    经典吃豆人游戏与源代码.zip

    迷宫中含有许多点点和名为Blinky,Pinky,Inky和Clyde的幽灵。目标是吃掉迷宫中的所有点点。幽灵会在迷宫中四处游走,试图消灭吃豆人。玩家通过箭头键控制游戏角色。四个幽灵会在迷宫中寻找吃豆人,如果任何一个幽灵...

    使用 JavaScript 制作的 Canvas Pacman 游戏及其源代码.zip

    迷宫中有各种圆点和幽灵,分别名为 Blinky、Pinky、Inky 和 ​​Clyde。目标是吃掉迷宫中的所有圆点。幽灵会在迷宫中四处奔跑,试图杀死吃豆人。在这个游戏中,控制是箭头键。 演示: 该项目为国外大神项目,可以...

    谷歌吃豆子游戏的

    1. **游戏机制**:Pac-Man是一款迷宫追逐游戏,玩家控制黄色的Pac-Man角色,通过吃掉迷宫中的所有豆子来得分,同时避开鬼魂(Blinky、Pinky、Inky和Clyde)。 2. **游戏规则**:Pac-Man可以吃掉特殊的“能量豆”,...

    CSIE1601-U201610504-刘逸帆-吃豆人设计说明1

    《吃豆人》是一款经典的二维过关游戏,它由日本南梦宫公司在1980年推出,以其简洁的游戏机制和丰富的趣味性赢得了全球玩家的喜爱。这款游戏旨在考验玩家的反应速度和策略规划,适合各个年龄段的玩家,特别是儿童,能...

Global site tag (gtag.js) - Google Analytics