精华内容
下载资源
问答
  • 数据结构(C++)有关练习题

    热门讨论 2008-01-02 11:27:18
    2、 针对带附加头结点单链表,试编写下列函数: A. 定位函数Locate:在单链表中寻找第i个结点。若找到,则函数返回第i个结点地址;若找不到,则函数返回NULL; B. 球最大值函数max:通过单链表一...
  • 当你测试Box2D数据结构,并使其跟自己游戏引擎中的对象结合起来时,这样做是比较方便。 举个典型例子,角色上附有物体,并在物体中附加角色指针,这就构成了一个循环引用。如果你有角色(actor),你就能得到...

    Chapter 11 杂项

    11.1 用户数据

    b2Fixture, b2Body 和 b2Joint 类都允许你通过一个 void 指针来附加用户数据。当你测试Box2D数据结构,并使其跟自己游戏引擎中的对象结合起来时,这样做是比较方便的。
    举个典型的例子,角色上附有物体,并在物体中附加角色的指针,这就构成了一个循环引用。如果你有角色(actor),你就能得到物体。如果你有物体,你也能得到角色。

    GameActor* actor = GameCreateActor();
    b2BodyDef bodyDef;
    bodyDef.userData = actor;
    actor->body = box2Dworld->CreateBody(&bodyDef);

    一些需要用户数据的例子:
    • 使用碰撞结果给角色施加伤害效果。
    • 当玩家进入一个包围盒(axis-aligned box)时,触发脚本事件。
    • 当Box2D通知你关节将要被摧毁时,去访问某个游戏结构。
    记住,用户数据是可选的,并且能放入任何东西。然而,你需要确保一致性。例如,如果你想在body中保存actor的指针,那你就应该在所有的 body中都保存actor指针。不要在一个body中保存actor指针,却在另一个body中保存foo指针。将一个actor指针强制转成foo指 针,可能会导致程序崩溃。
    用户数据指针默认为NULL。
    对于fixture来说,你可以定义一个用户数据结构来存储游戏特定的信息。例如材料类型、特效钩子、音效钩子,等等。

    struct FixtureUserData
    {
      int materialIndex;
      …
    };
    
    FixtureUserData myData = new FixtureUserData;
    myData->materialIndex = 2;
    
    b2FixtureDef fixtureDef;
    fixtureDef.shape = &someShape;
    fixtureDef.userData = myData;
    
    b2Fixture* fixture = body->CreateFixture(&fixtureDef);
    …
    delete fixture->GetUserData();
    fixture->SetUserData(NULL);
    body->DestroyFixture(fixture);

    11.2 隐式摧毁

    Box2D没有使用引用计数。因此你摧毁了body后,它就确实不存在了。访问指向已摧毁body的指针,会导致未定义的行为。 也就是说,你的程序可能会崩溃。以debug方式编译出的程序,Box2D的内存管理器会将已被摧毁实体占用的内存,都填上FDFDFDFD。在某些时候, 这样做可以使你更容易的找到问题的所在,并进而修复问题。
    如果你摧毁了Box2D实体,你要确保所有指向这实体的引用都被移除。如果只有实体的单个引用,处理起来就很简单了。但如果有多个引用,你需要考虑是否去实现一个句柄类(handle class),将原始指针封装起来。
    当你使用Box2D时,会很频繁的创建并摧毁很多的物体(bodies)、形状(shapes)和关节(joints)。某种程度上,这些实体是由Box2D自动管理的。当你摧毁了一个body,所有跟它有关联的形状、关节和接触都会自动被摧毁。这称为隐式摧毁。
    连接到这些关节或接触上的物体会被唤醒。这个过程是很简便的,但是你必须小心一个关键问题:

    注意
    当body被摧毁时,所有依附其上的形状和关节也会被自动摧毁。你应该将任何指向这些形状和关节的指针清零。否则如果你之后试图访问或者再次摧毁这些形状或关节,你的程序会死得很惨。

    为了帮助你清空关节指针,Box2D提供了一个名叫b2DestructionListener的监听类。你可以实现这个类,并将world对象传给它。这样当关节被隐式摧毁时,world对象会通知你。
    注意,关节或fixture被显式摧毁时,并没有通知。这种情况下,所有者是清楚的,你可以在合适的地方执行必要的清理工作。如果你喜欢,你也可以调用你自己的b2DestructionListener的实现来保持清理代码的集中。
    大多数情况下,隐式摧毁还是很方便的,但也很容易使你的程序崩溃。你可能会将指向shape或关节的指针保存起来,当有关联的body摧毁时,这些指针就变得无效了。当关节是由那些与相关的物体的管理代码无关的代码片段创建时,事情会变得更糟糕。比如,testbed就创建了一个 b2MouseJoint用来交互式操作屏幕上的body。
    Box2D提供了一种回调机制,用于在隐式摧毁发生时,通知你的应用程序。这给了你的程序一个机会,将无效指针清零。这个回调机制将在稍后详述。
    你可以实现一个b2DestructionListener,这样当一个形状或关节隐式摧毁时,b2World 就能通知你。这将防止你的代码访问野指针。

    class MyDestructionListener : public b2DestructionListener
    {
        void SayGoodbye(b2Joint* joint)
        {
            // remove all references to joint.
        }
    };

    你可以将摧毁监听器(destruction listener)注册到world对象中。在world对象初始化时,你就应该这样做了。

    myWorld->SetListener(myDestructionListener);

    11.3 像素和坐标系统

    重申一下Box2D使用MKS(米、千克和秒)单位制,角度使用弧度为单位。你可能对使用米感到困惑,因为你的游戏是以像素的形式表示的。在testbed中为了解决这个问题,我将整个游戏都使用米,并使用OpenGL的视图转换,将world调整到屏幕空间中。

    float lowerX = -25.0f, upperX = 25.0f, lowerY = -5.0f, upperY = 25.0f;
    gluOrtho2D(lowerX, upperX, lowerY, upperY);

    如果你的游戏必须使用像素为单位,在向Box2D传送值的时候,你应该将你的长度单位由像素转换成米。反之,当你接收Box2D传来的值时,应该将之由米转换成像素。这会提升物理模拟的稳定性。
    你必须设置一个合理的转换因子。我建议可以根据你的角色的尺寸来做出选择。假设你每米使用50个像素(因为你的角色有75个像素的高度),则你可以使用下面这些公式将像素转换成米:

    xMeters = 0.02f * xPixels;
    yMeters = 0.02f * yPixels;

    相反的:

    xPixels = 50.0f * xMeters;
    yPixels = 50.0f * yMeters;

    你应该在你的代码中使用MKS单位制,只在你渲染的时候,将之转换成像素。这会简化你的游戏逻辑,并减少错误的可能。因为渲染时的变换,能被限定在很小的代码中。
    如果你使用转换因子,你应该在全局范围内调整它,确保没有错误发生。你也可以调整它来改善稳定度。

    展开全文
  • 为了使这一动作明智,需要附加的“现实约束”,并且需要BRST循环的概念来找到具有这些附加约束理论超对称变换。 该理论拥有三个可能质量项。 较早一篇论文用第一个质量术语检验了该理论。 本文增加了第二个...
  • 4.3.3 与垃圾回收的附加选项 4.3.4 常见垃圾回收器 4.4 内存管理的小技巧 4.4.1 尽量使用直接量 4.4.2 使用StringBuilder和StringBuffer进行字符串连接 4.4.3 尽早释放无用对象的引用 4.4.4 尽量少用静态变量...
  •  在本书中,李刚老师一改枯燥的教学方式,专门面向Java初学者可能会遇到的各种学习问题,由点及面,详细讨论了Java内存管理、Java编程过程中常遇陷阱、常用数据结构的Java实现和Java程序开发的方法与经验等内容。...
  • 循环结构 数据结构 算法 面向对象的编程思想 容器/集合 ArryList HashMap AWT/SWING (做界面,只做了解基本已经用不到) IO流 多线程 网络编程 (服务器由来) JDBC (与数据库进行交互技术...

    1. JavaSE

    • 基础知识

      • 顺序结构
      • 选择结构
      • 循环结构
    • 数据结构

    • 算法

    • 面向对象的编程思想

    • 容器/集合

      1. ArryList
      2. HashMap
    • AWT/SWING (做界面,只做了解基本已经用不到)

    • IO流

    • 多线程

    • 网络编程 (服务器的由来)

    • JDBC (与数据库进行交互的技术)

    • 附加:正则表达式/注解/反射/xml技术/设计模式


    2. 前端

    • HTML5

    • css

    • js

    • jQuery (js的封装库)

    • Ajax 局部页面的刷新技术(接口)

    • 模板

      • 必须熟练掌握一个后台模板 例如:x-admin
        因为在做web开发时,大部分工作在做后台开发
    • 框架

      • easyUI vue layUI bootrap Semantic-ui

    3. 数据库 关系型数据库

    1. 先学习Oracle,在过一下MySQL.内容的差不多
    2. 熟练掌握SQL语句编写、SQL的优化
    3. 数据库表的设计
    • Oracle 大型数据库

      可视化工具:plsql
    • MySQL 中小型数据库

      可视化工具:Navicat、splyog
    • spl sever (很少用,用到在了解)


    4. JavaWeb 基础

    1. 这是传统的web开发模式 前后端交互的方式用到jsp
    2. 必须熟练掌握
    • jsp(逐渐淘汰可以先不学)

    • Servelet

    • 中间穿插的技术点:

      Session、Cookie、Ajex、JSTL标签库、EL表达式、过滤器(fillter)、监听器(Listener)
    • Tomcat 服务器(网络编程)


    5. JavaEE的核心技术(三大框架+其他框架技术)

    • Mybaits

      按照项目的结构:属于持久层,也就是数据库层,与数据库打交道
    • Spring

      1. IOC 理解依赖注入,控制反转
      2. AOP 横切一刀
    • Spring MVC

    • Git/GitHub 版本控制器,团队合作开发

    • Maven 项目构建工具(极其重要)

    • WMware 虚拟机

      学习Linux系统、centOS入手、软件下载和安装、基本命令
    • VSFTPD+Nginx 搭建图片服务器

      在Linux下搭建图片服务器 文件上传和下载(JavaSE-IO)
    • 分布式项目

      Dubbo、zookeeper、Solr、SSO、shiro

    6.JavaEE新型开发-微服务

    • Spring Boot

      • 必须掌握
      • 和Spring对比减少了一些配置文件增加了约束
      • 约束>配置
    • Spring Data

      • Spring Data JPA 数据库层的,更能适应springboot的项目,mybatis和springboot整合也行
    • Spring Securiy

      • 是spring推出的一门安全框架,类似于Shior,对springboot项目的兼容性更好
    • Spring Clould

      • 技术在不断更新,需要不断的学习

    7、运维Linux系统

    • Linux系统的基础知识

      • Linux的常用命令
      • 软件下载和安装
    • docker容器 技术

      • 通过安装docker容器,对软件进行快速的安装

    转载自 叶仁平 闲谈一年学习Java的心得感受
    在作者的基础上稍微进行了修改

    展开全文
  • 程序经常需要执行重复性操作,为此C++提供了3种循环结构:for循环、while循环和dowhile循环。这些循 环必须知道何时终止,C++关系操作符使程序员能够创建测试来引导循环。本章还将介绍如何创建逐字符 地读取和...
  • 程序经常需要执行重复性操作,为此C++提供了3种循环结构:for循环、while循环和dowhile循环。这些循 环必须知道何时终止,C++关系操作符使程序员能够创建测试来引导循环。本章还将介绍如何创建逐字符 地读取和...
  • 程序经常需要执行重复性操作,为此C++提供了3种循环结构:for循环、while循环和dowhile循环。这些循 环必须知道何时终止,C++关系操作符使程序员能够创建测试来引导循环。本章还将介绍如何创建逐字符 地读取和...
  • 程序经常需要执行重复性操作,为此C++提供了3种循环结构:for循环、while循环和dowhile循环。这些循 环必须知道何时终止,C++关系操作符使程序员能够创建测试来引导循环。本章还将介绍如何创建逐字符 地读取和...
  • 习题43 基本面向对象分析和设计 134 简单游戏引擎分析 135 把问题写下来或者画出来 135 摘录和研究关键概念 135 为各种概念创建类层次结构图和 对象关系图 136 编写和运行各个类 137 重复和优化 139 自...
  • 建设展会的互联网商务平台,发布展会信息,有效利用网络优势进行展会推广、展会招商、展位预定、服务合作、服务预定、参展商信息发布、网上观众预定、网上调研等,建立包含多功能的大型数据库,采用三层结构的应用...
  • 本书结构非常简单,共包括52个习题,其中26个覆盖了输入/输出、变量和函数三个主题,另外26个覆盖了一些比较高级话题,如条件判断、循环、类和对象、代码测试及项目实现等。每一章格式基本相同,以代码习题...
  • 笨办法学python

    2018-07-22 23:37:07
    本书结构非常简单,共包括52个习题,其中26个覆盖了输入/输出、变量和函数三个主题,另外26个覆盖了一些比较高级话题,如条件判断、循环、类和对象、代码测试及项目实现等。每一章格式基本相同,以代码习题...
  • 本书结构非常简单,除“准备工作”之外,还包括52个习题,其中26个覆盖了输入/输出、变量和函数3个主题,另外26个覆盖了一些比较进阶话题,如条件判断、循环、类和对象、代码测试及项目实现等。每一章格式基本...
  • 持续交付-发布可靠软件系统方法

    热门讨论 2014-03-02 01:32:14
    9.8 容量测试系统的附加价值 201 9.9 小结 202 第10 章 应用程序的部署与发布 203 10.1 引言 203 10.2 创建发布策略 204 10.2.1 发布计划 205 10.2.2 发布产品 205 10.3 应用程序的部署和晋级 206 10.3.1 ...
  • 9.8 容量测试系统的附加价值 201 9.9 小结 202 第10章 应用程序的部署与发布 203 10.1 引言 203 10.2 创建发布策略 204 10.2.1 发布计划 205 10.2.2 发布产品 205 10.3 应用程序的部署和晋级 206 ...
  • 这些附加的功能包括从画线宽度和颜色到画文本时所用字体。图形对象类封装了所有六个图形对象。 下面表格列出了MFC图形对象类: MFC类 图形对象句柄 图形对象目的 CBitmap HBITMAP 内存中位图 CBrush ...
  • oc block详解

    2016-11-08 19:42:18
    __block另外一个比较多使用场景是,为了避免某些情况下Block循环引用问题,我们也可以给相应对象加上__block 修饰符。今天为大家详细讲解Block实现详细过程 block实际结构 block类型 block是如何捕捉变量

    oc block详解

    我们知道在Block使用中,Block内部能够读取外部局部变量的值。但我们需要改变这个变量的值时,我们需要给它附加上__block修饰符。__block另外一个比较多的使用场景是,为了避免某些情况下Block循环引用的问题,我们也可以给相应对象加上__block 修饰符。今天为大家详细讲解Block实现的详细过程

    • block的实际结构
    • block的类型
    • block是如何捕捉变量的
    • 不同类型的block的复制
    • ARC中block的工作
    • 外部变量的复制

    block的实际结构
    下面是block的数据结构定义

    struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *);
    };
    
    struct Block_layout {
        void *isa;
        int flags;
        int reserved;
        void (*invoke)(void *, ...);
        struct Block_descriptor *descriptor;
        /* Imported variables. */
    };

    通过该图,我们可以知道,一个 block 实例实际上由 6 部分构成:
    1. isa 指针,所有对象都有该指针,用于实现对象相关的功能。
    2. flags,用于按 bit 位表示一些 block 的附加信息。
    3. reserved,保留变量。
    4. invoke,函数指针,指向具体的 block 实现的函数调用地址。
    5. descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
    6. variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

    了解block数据结构后,我们接下来对block进行底层解析,首先看一段最简单的block代码:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            ^{ };
        }
        return 0;
    }

    利用clang编译器把这段代码编译成c++的实现,我们会获得一个.cpp文件,打开它,可以从它最底部获得如下代码:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
        }
        return 0;
    }

    不难看出其中的__main_block_impl_0就是block的一个C++的实现(最后面的_0代表是main中的第几个block),整个结构和之前的所讲的结构是完全吻合的。在定义__main_block_impl_0结构体时,同时创建了__main_block_func_0__main_block_desc_0的对象__main_block_desc_0_DATA__main_block_func_0为block实现函数(因为block为空,所以实现函数也为空),__main_block_desc_0_DATA为block的附加描述信息。并且在main函数中利用__main_block_impl_0的构造函数将上面两个参数传入进去。这就是对一个简单的block块的底层解析。


    block的类型
    block的常见类型有3种:

    • _NSConcreteGlobalBlock(全局)
    • _NSConcreteStackBlock(栈)
    • _NSConcreteMallocBlock(堆)

    附上UNIX的进程虚拟内存段分布图:
    这里写图片描述

    • _NSConcreteStackBlock 保存在栈中的block,出栈时会被销毁
    • _NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量
    • _NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁

    注意:分配在全局变量上的Block,在变量作用域外也可以通过指针安全的访问。但分配在栈上的Block,如果它所属的变量作用域结束,该Block就被废弃。同样地,__block变量也分配在栈上,当超过该变量的作用域时,该__block变量也会被废弃。

    这时候_NSConcreteMallocBlock就显得格外重要了。Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题。将分配到栈上的Block复制到堆上,这样但栈上的Block超过它原本作用域时,堆上的Block还可以继续存在。由于block的复制最终都会调用_Block_copy_internal函数,所以观察这个函数就可以知道堆中block是如何被创建的了:

    static void *_Block_copy_internal(const void *arg, const int flags) {
        struct Block_layout *aBlock;
        ...
        aBlock = (struct Block_layout *)arg;
        ...
        // Its a stack block.  Make a copy.
        if (!isGC) {
            // 申请block的堆内存
            struct Block_layout *result = malloc(aBlock->descriptor->size);
            if (!result) return (void *)0;
            // 拷贝栈中block到刚申请的堆内存中
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
            result->flags |= BLOCK_NEEDS_FREE | 1;
            // 改变isa指向_NSConcreteMallocBlock,即堆block类型
            result->isa = _NSConcreteMallocBlock;
            if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
                //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
                (*aBlock->descriptor->copy)(result, aBlock); // do fixup
            }
            return result;
        }
        else {
            ...
        }
    }

    从以上代码以及注释可以很清楚的看出,函数通过memmove将栈中的block的内容拷贝到了堆中,并使isa指向了_NSConcreteMallocBlock
    block主要的一些学问就出在栈中block向堆中block的转移过程中了。


    block是如何捕捉变量的
    我们通过编译转换捕捉不同类型的block,以对他们进行区别。
    局部变量
    前:

    - (void)test
    {
        int a;
        ^{a;};
    }

    后:

    struct __Person__test_block_impl_0 {
      struct __block_impl impl;
      struct __Person__test_block_desc_0* Desc;
      int a;
      // a(_a)是构造函数的参数列表初始化形式,相当于a = _a。从_I_Person_test看,传入的就是a
      __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
    a;}
    
    static struct __Person__test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};
    
    static void _I_Person_test(Person * self, SEL _cmd) {
        int a;
        (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, a);
    }

    可以看到,block的__Person__test_block_impl_0相对于文章开头增加了一个int类型的成员变量,他就是用来存储外部变量a的。可以看出,在 _I_Person_test函数中的block的构造函数中参数a,只是一次值传递。因此当我们想在block中进行以下操作时,将会发生错误:

    ^{a = 10;};

    因为_I_Person_test函数中的a和Persontest_block_func_0函数中的a并没有在同一个作用域,所以在block对a进行赋值是没有意义的,所以编译器给出了错误。或许你想如果这样写:

    - (void)test
    {
        int a = 0;
        // 利用指针p存储a的地址
        int *p = &a;
    
        ^{
            // 通过a的地址设置a的值
            *p = 10;
        };
    }

    这样的确能改变a 的值,但是如果block是跨栈的,a 实际上已经被销毁,这样会造成野指针错误。

    全局变量
    前:

    // 全局静态
    static int a;
    // 全局
    int b;
    - (void)test
    {
    
        ^{
            a = 10;
            b = 10;
        };
    }

    后:

    static int a;
    int b;
    
    struct __Person__test_block_impl_0 {
      struct __block_impl impl;
      struct __Person__test_block_desc_0* Desc;
      __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
            a = 10;
            b = 10;
        }
    
    static struct __Person__test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};
    
    static void _I_Person_test(Person * self, SEL _cmd) {
        (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA);
    }

    因为此时全局变量在程序结束前不会被销毁。__Person__test_block_impl_0也并没有相应的变量a、b。__Person__test_block_func_0实现函数所访问的变量为全局变量。因此不会发生错误。

    局部静态变量

    - (void)test
    {
        static int a;
        ^{
            a = 10;
        };
    }

    利用clang编译后得到:

    struct __Person__test_block_impl_0 {
      struct __block_impl impl;
      struct __Person__test_block_desc_0* Desc;
      int *a;
      __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
      int *a = __cself->a; // bound by copy
            // 这里通过局部静态变量a的地址来对其进行修改
            (*a) = 10;
        }
    
    static struct __Person__test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};
    
    static void _I_Person_test(Person * self, SEL _cmd) {
        static int a;
        // 传入a的地址
        (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, &a);
    }

    静态局部变量相当于全局变量(只是只有在这个函数中能访问,但是生命周期是和全局变量差不多的),函数退出之后变量还在,因此为了block能够访问一个有效的变量。在__Person__test_block_impl_0构造函数中传入了a的地址。

    __block修饰的变量

    - (void)test
    {
       __block int a;
        ^{
            a = 10;
        };
    }

    利用clang编译后得到:

    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    
    struct __Person__test_block_impl_0 {
      struct __block_impl impl;
      struct __Person__test_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
      __Block_byref_a_0 *a = __cself->a; // bound by ref
            // 注意,这里的_forwarding用来保证操作的始终是堆中的拷贝a,而不是栈中的a
            (a->__forwarding->a) = 10;
        }
    static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __Person__test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
      void (*dispose)(struct __Person__test_block_impl_0*);
    } __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
    
    static void _I_Person_test(Person * self, SEL _cmd) {
        // __block将a包装成了一个对象
       __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0)};
    ;
        (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
    }

    对比上面的结果,明显多了__Block_byref_a_0结构体,这个结构体中含有isa指针,所以也是一个对象,它是用来包装局部变量a的。当block被copy到堆中时,__Person__test_block_impl_0的拷贝辅助函数__Person__test_block_copy_0会将__Block_byref_a_0拷贝至堆中,当一个Block被复制到堆上时,与之相关的__block变量也会被复制到堆上,此时堆上的Block持有相应堆上的__block变量。所以即使局部变量所在堆被销毁,block依然能对堆中的局部变量进行操作。栈上的__block变量被复制到堆上之后,会将成员变量__forwarding的值替换为堆上的__block变量的地址。这个时候block通过以下代码访问堆上的变量:

    (a.__forwarding->a)

    两个辅助函数__Block_byref_id_object_copy__Block_byref_id_object_dispose,以实现对对象内存的管理._Block_byref_assign_copy函数持有该变量(相当于retain)。当堆上的Block被废弃时,会使用_Block_object_dispose函数释放该变量(相当于release)。

    没有__block修饰对象

    typedef void(^Block)();
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSObject *a = [[NSObject alloc] init];
            Block block = ^ {
                a;
            };
        }
        return 0;
    }

    首先,在没有__block修饰时,对象编译转换的结果如下,删除了一些变化不大的代码:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      NSObject *a = __cself->a; // bound by copy
                a;
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0),

    对象在没有__block修饰时,并没有产生__Block_byref_a_0结构体,只是将标志位修改为BLOCK_FIELD_IS_OBJECT。而在_Block_object_assign中对应的判断分支代码如下:

    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        _Block_retain_object(object);
        _Block_assign((void *)object, destAddr);
    }
    ...

    可以看到,block复制时,会retain捕捉对象,以增加其引用计数。

    有__block修饰

    typedef void(^Block)();
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block NSObject *a = [[NSObject alloc] init];
            Block block = ^ {
                a;
            };
        }
        return 0;
    }

    在这种情况下,编译转换的部分结果如下:

    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSObject *a;
    };
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
    attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131,....};
    Block block = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
    }
    
    // 以下的40表示__Block_byref_a_0对象a的位移(4个指针(32字节)+2个int变量(8字节)=40字节)
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }

    从上面我们可以总结:
    1. 对于基本类型,如果加了__block字段会把基本类型包装成__Block_byref对象。如果没有加__block字段,仅仅只是进行了赋值传递。如果改变了基本类型的值,则会发生错误。
    2. 对于对象类型,如果加了__block字段同样会把包装成__Block_byref对象中,另外增加了两个辅助函数__Block_byref_id_object_copy__Block_byref_id_object_dispose,以实现对对象内存的管理。如果没有加__block字段,仅仅是进行retain一次。

    self隐式循环引用

    @implementation Person
    {
        int _a;
        void (^_block)();
    }
    - (void)test
    {
      void (^_block)() = ^{
            _a = 10;
        };
    }
    
    @end

    利用clang编译后得到:

    struct __Person__test_block_impl_0 {
      struct __block_impl impl;
      struct __Person__test_block_desc_0* Desc;
      // 可以看到,block强引用了self
      Person *self;
      __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
      Person *self = __cself->self; // bound by copy
    
            (*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;
        }
    static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __Person__test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
      void (*dispose)(struct __Person__test_block_impl_0*);
    } __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
    
    static void _I_Person_test(Person * self, SEL _cmd) {
      void (*_block)() = (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344);
    }

    在调用__Person__test_block_impl_0构造函数时,将self传入进去了。block 中引用person,person引用block。造成了循环引用。

    不同类型block的复制

    • 栈block复制到堆block
      从以下代码可以看出,栈block的复制不仅仅复制了其内容,还添加了一些额外的东西
    struct Block_layout *result = malloc(aBlock->descriptor->size);
       if (!result) return (void *)0;
       memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
       // reset refcount
       result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
       result->flags |= BLOCK_NEEDS_FREE | 1;
       result->isa = _NSConcreteMallocBlock;
       if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
           //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
           (*aBlock->descriptor->copy)(result, aBlock); // do fixup
       }
       return result;

    flags中并入了BLOCK_NEEDS_FREE(这个标志表明block需要释放,在release以及再次拷贝时会用到)。如果有辅助copy函数(BLOCK_HAS_COPY_DISPOSE),那么就调用(这个辅助copy函数是用来拷贝block捕获的变量的)。

    • 堆block,对于堆block得复制。仅仅是利用latching_incr_int操作,其功能就是让block的引用计数加1。所以堆中block的拷贝只是单纯地改变了引用计数
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }
    • 全局block从以下代码看出,对于全局block,函数没有做任何操作,直接返回了传入的block
      if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }

    ARC中block的工作
    看下面代码:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int i = 10;
            void (^block)() = ^{i;};
    
            __block void (^weakBlock)() = ^{i;};
    
            void (^stackBlock)() = ^{};
    
            // ARC情况下
    
            // 创建时,都会在栈中
            // <__NSStackBlock__: 0x7fff5fbff778>
            NSLog(@"%@", ^{i;});
    
            // 因为stackBlock为strong类型,且捕获了外部变量,所以赋值时,自动进行了copy
            // <__NSMallocBlock__: 0x100300200>
            NSLog(@"%@", block);
    
            // 如果是weak类型的block,依然不会自动进行copy
            // <__NSStackBlock__: 0x7fff5fbff7a8>
            NSLog(@"%@", weakBlock);
    
            // 如果block是strong类型,并且没有捕获外部变量,那么就会转换成__NSGlobalBlock__
            // <__NSGlobalBlock__: 0x1000010d0>
            NSLog(@"%@", stackBlock);
    
            // 在非ARC情况下,产生以下输出
            // <__NSStackBlock__: 0x7fff5fbff750>
            // <__NSStackBlock__: 0x7fff5fbff7d8>
            // <__NSStackBlock__: 0x7fff5fbff780>
            // <__NSGlobalBlock__: 0x1000010b0>
    
        }
        return 0;
    }

    从上面代码可得:

    • 在ARC中,类型为strong且捕获了外部变量(局部非静态变量)的block进行了copy。
    • 在ARC中,类型为strong但没有捕获了外部变量(若捕获的是静态变量或者是全局变量)的block转化成全局类型。
    • 在ARC中,类型为weak即使捕获了外部变量也不会进行copy。
    • 在非ARC,如果没有捕获任何外部变量的block转化成全局类型,block的复制必须手动调用[block copy]来复制。

    block作为参数传递

    NSMutableArray *arrayM;
    void myBlock()
    {
        int a = 5;
        Block block = ^ {
            NSLog(@"%d", a);
        };
    
        [arrayM addObject:block];
        NSLog(@"%@", block);
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            arrayM = @[].mutableCopy;
    
            myBlock();
    
            Block block = [arrayM firstObject];
            // 非ARC这里崩溃
            block();
     }
    
    // ARC情况下输出
    // <__NSMallocBlock__: 0x100214480>
    
    // 非ARC情况下输出
    // <__NSStackBlock__: 0x7fff5fbff738>
    // 崩溃,野指针错误

    因此有些 ARC情况下因为自动执行了copy,所以返回类型为__NSMallocBlock__,在函数结束后依然可以访问;而非ARC情况下,否则因为栈中的block生命周期和函数中的栈生命周期关联,当函数退出后,相应的堆被销毁,block也就不存在了,就有可能造成错误。
    在上面如果删除以下代码:

    NSLog(@"%d", a);

    那么block就会变成全局类型,在main中访问也不会出崩溃。

    block作为返回值
    在非ARC情况下,如果返回值是block,则一般这样操作:
    return [[block copy] autorelease];
    对于外部要使用的block,更趋向于把它拷贝到堆中,使其脱离栈生命周期的约束。


    外部变量的复制

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
    //        情景四
    //        __block NSObject * i = [[NSObject alloc]init];
    //        NSLog(@"%@",i);
    //        void (^block)() = ^{
    //            i;
    //            NSLog(@"%@",i);
    //        };
    //        block();
    //        NSLog(@"%@",block);
    //        NSLog(@"%@",i);
    //        2016-11-08 22:11:38.116 test[12799:1096533] <NSObject: 0x100201af0>
    //        2016-11-08 22:11:38.117 test[12799:1096533] <NSObject: 0x100201af0>
    //        2016-11-08 22:11:38.117 test[12799:1096533] <__NSMallocBlock__: 0x100201990>
    //        2016-11-08 22:11:38.117 test[12799:1096533] <NSObject: 0x100201af0>
            //        情景三
    //        NSObject * i = [[NSObject alloc]init];
    //        NSLog(@"%@",i);
    //        void (^block)() = ^{
    //            i;
    //            NSLog(@"%@",i);
    //        };
    //        block();
    //        NSLog(@"%@",block);
    //        NSLog(@"%@",i);
    //        2016-11-08 22:00:52.401 test[12741:1093395] <NSObject: 0x100204800>
    //        2016-11-08 22:00:52.402 test[12741:1093395] <NSObject: 0x100204800>
    //        2016-11-08 22:00:52.402 test[12741:1093395] <__NSMallocBlock__: 0x100100200>
    //        2016-11-08 22:00:52.402 test[12741:1093395] <NSObject: 0x100204800>
    
    
    //        情景二
    //        __block int i = 23131;
    //        NSLog(@"%x",&i);
    //        void (^block)() = ^{
    //            i = 10;
    //            NSLog(@"%x",&i);;
    //        };
    //        block();
    //        NSLog(@"%x",&i);
    //        2016-11-08 21:56:16.766 test[12689:1090903] 5fbff808
    //        2016-11-08 21:56:16.768 test[12689:1090903] 6009c8
    //        2016-11-08 21:56:16.768 test[12689:1090903] 6009c8
    
    //        情景一
    //        int i = 23131;
    //        NSLog(@"%x",&i);
    //        void (^block)() = ^{
    //            NSLog(@"%x",&i);;
    //        };
    //        block();
    //        NSLog(@"%x",&i);
    //        2016-11-08 21:57:05.581 test[12710:1091507] 5fbff80c
    //        2016-11-08 21:57:05.581 test[12710:1091507] 404b90
    //        2016-11-08 21:57:05.582 test[12710:1091507] 5fbff80c
    
        }
        return 0;
    }

    情景一:由于是赋值传值。block里面的变量i是block的局部变量,它和外面的变量i不属于同一作用域。是两个不同的变量,因此第二个i的地址与第1和3的不同。

    情景二:block捕获到外部变量后,block将变量i从栈复制到堆中。因此第二个地址和第一个会有所不同。当block为__NSMallocBlock后,后面对栈中a的访问,编译器会把它全部转换成对堆中a的访问。所以第二次和第三次相同。

    情景三和情景四:因为oc对象本来就存储在堆中,block仅仅对oc对象进行retain或release,因此其地址是一直不会变的。


    参考博客
    黑幕背后的__block修饰符
    Block技巧与底层解析
    谈Objective-C block的实现

    展开全文
  • 笨办法学Python

    2018-03-07 13:45:54
    本书结构非常简单,共包括52个习题,其中26个覆盖了输入/输出、变量和函数三个主题,另外26个覆盖了一些比较高级话题,如条件判断、循环、类和对象、代码测试及项目实现等。每一章格式基本相同,以代码习题...
  • 本书结构非常简单,除“准备工作”之外,还包括52个习题,其中26个覆盖了输入/输出、变量和函数3个主题,另外26个覆盖了一些比较进阶话题,如条件判断、循环、类和对象、代码测试及项目实现等。每一章格式...
  • Loose Ends

    2021-01-03 22:46:28
    当您检查Box2D数据结构并且想要确定它们与游戏引擎中的对象之间关系时,这非常方便。 例如,通常将角色指针附加到该角色上刚体。 这将建立一个循环引用。 如果有actor,就可以得到刚体。 如果有刚体,就可以...

    最后


    用户数据


    b2Fixture,b2Body和b2Joint类允许您将用户数据附加为uintptr_t。 当您检查Box2D数据结构并且想要确定它们与游戏引擎中的对象之间的关系时,这非常方便。

    例如,通常将角色指针附加到该角色上的刚体。 这将建立一个循环引用。 如果有actor,就可以得到刚体。 如果有刚体,就可以找到actor。

    GameActor* actor = GameCreateActor();
    b2BodyDef bodyDef;
    bodyDef.userData.pointer = reinterpret_cast<uintptr_t>(actor);
    actor->body = myWorld->CreateBody(&bodyDef);

    以下是一些需要用户数据的情况的示例:

    • 使用碰撞结果对actor造成伤害。
    • 如果玩家在轴对齐的框内,则执行脚本事件。
    • 当Box2D通知您关节将被破坏时,访问游戏结构。

    请记住,用户数据是可选的,您可以在其中添加任何内容。 但是,您应该保持一致。 例如,如果要将角色指针存储在一个刚体上,则应在所有刚体上保留角色指针。 不要将actor指针存储在一个刚体上,而将foo指针存储在另一个刚体上。 将actor指针强制转换为foo指针可能会导致崩溃。

    用户数据指针默认为0。

    对于固定装置,您可以考虑定义一个用户数据结构,该结构可用于存储游戏特定的信息,例如材质类型,效果hooks,声音hooks等。

    struct FixtureUserData
    {
        int materialIndex;
        // ...
    };
     
    FixtureUserData myData = new FixtureUserData;
    myData->materialIndex = 2;
     
    b2FixtureDef fixtureDef;
    fixtureDef.shape = &someShape;
    fixtureDef.userData.pointer = reinterpret_cast<uintptr_t>(myData);
     
    b2Fixture* fixture = body->CreateFixture(&fixtureDef);
    // ...
     
    delete fixture->GetUserData();
    body->DestroyFixture(fixture);

    自定义用户数据

    您可以定义嵌入在Box2D数据结构中的自定义数据结构。这是通过定义B2_USER_SETTINGS并提供文件b2_user_settings.h来完成的。有关详细信息,请参见b2_settings.h。

    隐性破坏

    Box2D不使用引用计数。因此,如果您摧毁了刚体,那真的就消失了。访问指向被破坏的刚体的指针具有未定义的行为。换句话说,您的程序可能会崩溃。为了帮助解决这些问题,调试构建内存管理器使用FDFDFDFD填充了损坏的刚体。在某些情况下,这可以帮助您更轻松地发现问题。

    如果销毁Box2D刚体,则要确保删除对销毁对象的所有引用。如果您只有一个对刚体的引用,这很容易。如果您有多个引用,则可以考虑实现一个句柄类来包装原始指针。

    通常,在使用Box2D时,您会创建和破坏许多刚体,形状和关节。 Box2D在某种程度上自动管理这些刚体。如果破坏了一个刚体,那么所有关联的形状和关节都会被自动破坏。这称为隐式破坏。

    销毁刚体时,其所有附着的形状,关节和接触点都会被销毁。这称为隐式破坏。连接到这些关节和/或触点之一的任何物体都被唤醒。这个过程通常很方便。但是,您必须意识到一个关键问题:

    注意:破坏刚体时,附着到该刚体的所有固定装置和关节都会自动破坏。 您必须使指向这些形状和关节的所有指针无效。 否则,如果您以后尝试访问或销毁这些形状或关节,则程序将崩溃。

    为了帮助您取消关节指针,Box2D提供了一个名为b2DestructionListener的侦听器类,您可以实现该类并将其提供给world对象。然后,当关节将被隐式破坏时,世界对象将通知您

    请注意,当关节或固定装置被明确销毁时,没有任何通知。在这种情况下,所有权很明确,您可以当场执行必要的清理。如果愿意,可以调用自己的b2DestructionListener实现,以使清理代码保持集中状态。

    在许多情况下,隐式销毁非常方便。但是它会使您的程序崩溃。您可以在代码中的某个位置存储指向形状和关节的指针。当关联的刚体被破坏时,这些指针变为孤立的。当您考虑到关节通常是由与关联刚体的管理无关的一部分代码创建时,情况会变得更糟。例如,测试平台创建了一个b2MouseJoint来对屏幕上的刚体进行交互式操作。

    Box2D提供了一种回调机制,可在发生隐式破坏时通知您的应用程序。这使您的应用程序有机会使孤立指针无效。此回调机制将在本手册后面介绍。

    您可以实现b2DestructionListener,该b2DestructionListener允许b2World通知您何时由于关联的刚体被破坏而隐式破坏了形状或关节。这将有助于防止您的代码访问孤立的指针。

    class MyDestructionListener : public b2DestructionListener
    {
        void SayGoodbye(b2Joint* joint)
        {
            // remove all references to joint.
        }
    };

    然后,您可以向您的world对象注册一个销毁侦听器的实例。 您应该在世界初始化期间执行此操作。

    myWorld->SetListener(myDestructionListener);

    像素和坐标系

    回想一下Box2D使用MKS(米,千克和秒)单位和弧度表示角度。 您可能无法使用米,因为您的游戏是以像素表示的。 为了在测试平台上解决这个问题,我将整个游戏工作都以米为单位,仅使用OpenGL视口转换将世界缩放到屏幕空间。

    float lowerX = -25.0f, upperX = 25.0f, lowerY = -5.0f, upperY = 25.0f;
    gluOrtho2D(lowerX, upperX, lowerY, upperY);

    如果您的游戏必须以像素为单位工作,则在从Box2D传递值时,应将长度单位从像素转换为米。 同样,您应该将从Box2D接收的值从米转换为像素。 这将提高物理模拟的稳定性。

    您必须提出一个合理的转换系数。 我建议您根据字符的大小进行选择。 假设您确定每米使用50个像素(因为您的角色高75像素)。 然后,您可以使用以下公式将像素转换为米:

    xMeters = 0.02f * xPixels;
    yMeters = 0.02f * yPixels;

    相反:

    xPixels = 50.0f * xMeters;
    yPixels = 50.0f * yMeters;

    您应该考虑在游戏代码中使用MKS单位,并且在渲染时仅将其转换为像素。 由于渲染转换可以隔离为少量代码,因此这将简化您的游戏逻辑并减少出错的机会。

    如果您使用转换因子,则应尝试对其进行全局调整,以确保没有任何损坏。 您也可以尝试对其进行调整以提高稳定性。

    调试图

    您可以实现b2DebugDraw类以获取物理世界的详细图形。 以下是可用的实体:

    • 形状轮廓
    • 关节连接
    • 广相轴对齐包围盒(AABB)
    • 重心

    这是绘制这些物理实体的首选方法,而不是直接访问数据。 原因是许多必要的数据是内部的,并且随时可能更改。

    该测试台使用调试绘图工具和接触侦听器绘制物理实体,因此它是如何实现调试绘图以及如何绘制接触点的主要示例。

    局限性

    Box2D使用几种近似值来有效地模拟刚体物理。这带来了一些限制。

    以下是当前限制:

    1. 在较轻的物体上堆放重物是不稳定的。质量比超过10:1时,稳定性会下降。
    2. 如果较轻的车身支撑较重的车身,则通过关节连接的车身链可能会拉伸。例如,连接到轻质物体链的破坏球可能不稳定。质量比超过10:1时,稳定性会下降。
    3. 与形状碰撞相比,通常存在大约0.5cm的倾斜形状。
    4. 连续碰撞不能处理关节。因此,您可能会在快速移动的物体上看到关节拉伸。
    5. Box2D使用辛欧拉积分方案。它不重现抛物线的抛物线运动,仅具有一阶精度。但是它是快速的并且具有良好的稳定性。
    6. Box2D使用迭代求解器来提供实时性能。您将无法获得精确的刚性碰撞或像素完美的精度。增加迭代次数将提高准确性。

     

     

     

     

     

     

     

     

    展开全文
  • 7.7.1 框架集文档中对象的结构 7.7.2 控制指定框架 7.7.3 常见属性和方法汇总 7.7.4 Frameset对象 7.7.5 iframe元素对象 7.8 Document对象 7.9 本章小结 第8章 Document对象 8.1 对象模型参考 8.2 Document对象 ...
  • 出处:风流的CG网络日志 时间:Mon, 28 Aug 2006 07:24:34 +0000 ...switch是对多重if-elseif-else结构的最好的替换: switch ($i) { case 0: print "i equals 0"; case 1: print "i equals 1"; case 2: print "i ...
  •  本书结构非常简单,共包括52个习题,其中26个覆盖了输入/输出、变量和函数三个主题,另外26个覆盖了一些比较高级话题,如条件判断、循环、类和对象、代码测试及项目实现等。每一章格式基本相同,以代码习题...
  • 应已知道程序是一系列语句集合,知道子程序/函数/宏是什么,知道象“If”这样控制语句,也知道象“while”这样的循环结构。注意这些东西在大量语言里都是类似。假如您学过一种宏语言,或者用过Perl之类...
  • 7.6.2 另一个处理结构的函数示例 7.6.3 传递结构的地址 7.7 函数和string对象 7.8 函数与array对象 7.9 递归 7.9.1 包含一个递归调用的递归 7.9.2 包含多个递归调用的递归 7.10 函数指针 7.10.1 函数指针的...
  • 说明: 此参数指定链接程序 (如: UNIX 中 ld, 或用于将目标文件链接到共享对象或 DLL GNU ld) 完整路径名。此参数是可选。随每个平台附带特有 make 文件中包含此参数默认值。如果为此参数指定了一个值...
  •  本书结构非常简单,共包括52个习题,其中26个覆盖了输入/输出、变量和函数三个主题,另外26个覆盖了一些比较高级话题,如条件判断、循环、类和对象、代码测试及项目实现等。每一章格式基本相同,以代码习题...
  • <p><code>Range</code> 对象包含了选区开始与结束信息,其中包括节点(node)与文本偏移量(offset)。节点信息不用多说,这里解释一下 offset 是指什么:例如,...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 170
精华内容 68
关键字:

循环结构的附加对象