文档章节

Raycast实现伪3d游戏(HTML5) (一)

李勇2
 李勇2
发布于 2015/03/02 09:38
字数 2636
阅读 74
收藏 0
点赞 0
评论 0

http://dev.opera.com/articles/view/creating-pseudo-3d-games-with-html-5-can-1/

参考此文~


纯粹的3d世界的够在现在已经非常的成熟了, 就是各种乱七八糟的技术非常之多,难免让人摸不着头脑。

那ok, 可以回到远古时代,追寻一些简单的方法,去实现一些simple的东西,毕竟这个世界的构造总是从simple到复杂的~

当然自己手动的将3d的世界展现在2d的平面上,多少有些软件绘制的味道,那么先从理论讲起。

raycast的意思,据我理解就是从人眼中发出光线,通过一个屏幕,最后和一个世界中的物体相交,探查到物体表面的一些性质,通常是颜色~

从这种最朴素的观点来看(古希腊人就有类似的关于光的概念),也很好的控制了世界的范围,所谓你的世界,就只是你所感知的世界。

但是如果只是上图的话还有些具体的问题,眼睛在哪里?屏幕多大?屏幕在哪里?视线的范围又有多大?人眼的视线射向无穷远处又该怎么办?

ok,从人头上方俯视可以假设这个水平的可视范围是90度的角, 屏幕的大小可以假设是320*200, 人眼正对屏幕中心。

ok,确定了人眼的范围,还需要确定屏幕到人眼的距离, 屏幕高度200, 宽度320, 你的眼睛角度是90, 那么一半是45度,这个距离应该不难吧,是160。

确定了人和屏幕,接着就要确定世界是什么样子,以及人应该在世界的什么地方。

一个普通的2d迷宫版的世界,周围都是墙,应该是一个比较简单的世界(室内世界)。有着天花板和地面, 以及一个四四方方的墙。


ok, 这个世界可以是1000*1000的大小,从头上看被切分成了10*10的小块,每块100*100大小(不要问我上面的图不对~我gimp很差的)

ok,现在有了世界,有了人,有了屏幕,那么接下来就是从眼睛里面发出光线了

通常游戏都只是一个循环。

function gameCycle()

{

     keyInput();

     draw();

     setTimeout(gameCycle, 1000/30);

}

游戏的实体包括玩家:

var player = {

     x:5,

     y:5,

    dir:0,

};

地图

var map = [

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],

[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],

[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],

[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],

[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],

[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],

[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],

[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],

];

这些1表示这个位置是墙,0表示没有东西, 下面需要探索这个世界了, 首先需要确定世界的坐标系是什么, 即原点,x轴,y轴,旋转的正方向~(额,就是逆时针还是顺时针旋转,和某个轴的夹角)

水平向右是x轴正方向,竖直向下是y轴正方向, 顺时针旋转是正方向,夹角是和x轴的夹角。

ok,有了这些琐碎麻烦的前提条件之后, player的信息也明确了。

玩家是在5*100, 5*100, 的地图位置, 面朝的是右侧。

但是上面只是明确了世界坐标, 我们还要明确屏幕坐标, 就是你现在看的这个屏幕的坐标系统~

原点也是在右上角, x轴水平向右, y轴正方向 水平向下~ 宽度320 高度 200~ 你现在距离屏幕160  墙高200, 中心在100的高度位置~


如何把玩家看到的东西画到屏幕上呢,我们需要从玩家到屏幕发射一条条的光线~

可以屏幕上每个像素和玩家的眼睛之间连线,画出一条光线, 但是这个数量有点多,可以认为所有墙都是垂直于地面的,那么,可以只绘制水平的几百条光线,而墙的在屏幕上的高度由距离来决定。


好吧现在,把你的世界和你的屏幕结合起来一起看看

那么我们的draw函数就是

function draw()

{

   //我们从屏幕的左到右发出光线

    for(var i  = 0; i < 320; i++)//320 条光线

    {

         var dir = [100,  -(i-100)];//光线在世界中的方向 世界中的 x和y方向~ 不要和屏幕 中的方向混淆了

         //那么沿着这个方向, 考虑玩家的位置 开始发送光线吧

        castRay(player.x, player.y,  dir[0], dir[1]);

    }

}

function castRay(oriX, oriY, dirX, dirY)

{    

}


有个问题,怎么发送光线来判断和哪堵墙相交, 好吧怎么判断一条射线和一个矩形相交 ??

首先这个世界被一个个的小方块分割, 我们可以从玩家所在的点 , 沿着光线的方向和一个个的小方块做相交测试。

好吧,第一步怎么一点点增加光线的长度, 第二步, 相交在小方块的哪条边上,边上的哪个点上???

据说最好的方法是 把x 和y轴分开分别进行测试, 因为x, y本身是平行线 并且和坐标轴平行, 根据平行线的理论, 被等距离平行线分割的线段的长度都是相等的,不信你看~

好吧我承认这个不太平行~ 至于为什么, 这个根据欧基里德的几何学,平行线之间的距离是他们的垂线段的长度, 这个长度相等, 而一条直线和平行线的夹角都是相等的(为什么?),那么简单的角角边, 或者直接直角三角形求变长都可以证明 斜线段相等。

当然特例是当斜线段本身垂直于平行线的时候, 需要额外考虑~

额,好吧,再追本溯源列出欧基里德的公理:

  公设1:任意一点到另外任意一点可以画直线。  

        公设2:一条有限线段可以继续延长。  

       公设3:以任意点为心及任意的距离可以画圆。  

       公设4:凡直角都彼此相等。  

       公设5:同平面内一条直线和另外两条直线相交,若在某一侧的两个内角和小于二直角的和,则这二直线经无限延长后在这一侧相交。

      好吧因为公理5, 所以夹角是相等的~~ 终于搞清了一个这个初等几何题

       当然还有一些显而易见的几何计算方法~

       一、等量间彼此相等 ;  二、等量加等量和相等 ;  三、等量减等量差相等 ;  四、完全重合的东西是相等的 ;  五、整体大于部分


好吧,现在我们成功的证明了平行线切分线段的长度相等, 那么我们发出的光线在前进的过程中 被x轴,y轴这些轴线切分的线段也相等的

那么处理过程可以分为两部分,

首先是沿x轴的平行线进行切分, 先求最近的和格子交点, 再每次移动一格的宽度,加上相应的高度,得到对应的格子编号,判断格子是否有墙,记录下当前和墙交点和玩家的距离


首先如何计算一条射线和一条一个方框的交点呢?

首先搞清射线在平面上如何表示,采用参数表示方法就是

(x, y) = (x0, y0) + k*(dx, dy) k>=0

其中x0, y0 是射线的出发点, dx, dy 是射线的方向, k 是参数

那么,如果是和竖直方向的轴相交, 那么x值就是 Math.ceil(player.x) * 100 ,(注意玩家当前是面向右边的)

相应的k就可以得到:k = (x-x0)/dx

那么相应的y值就是: y = y0+k*dy


ok,得到了初始点, 再计算每次沿x轴移动100, 对应的y移动的距离是:

DX = 100

DY = 100*dy/dx


ok, 在判断当前移动到的格子是否有墙存在:

Math.floor(x/100),  Math.floor(y/100)


代码如下:

function castRay(oriX, oriY, dirX, dirY)

{

      var x = oriX*100;

      var y = oriY*100;

      var k = dirY/dirX;

      var tx = Math.ceil(x);

      var ty = k*(tx-x)+y;

      //初次交点就是tx, ty 点

      var DX = 100;

      var DY = 100*dirY/dirX;

      var dist = 0;

      while(true)

     {

            var wallX = Math.floor(tx/100);

            var wallY = Math.floor(ty/100);

           if(map[wallX][wallY] != 0)

          {

                    break;

            }

            tx += DX;

            ty += DY;

      }

     

}

判断有墙了之后, 还需要计算玩家到墙交点的距离是多少, 这个简单, 根据欧基里德的距离公式:

dist = Math.sqrt( (tx-x)*(tx-x) + (ty-y)*(ty-y) )


知道了距离, 接着就是要把墙投影到屏幕上了, 假设墙高度都是200, 那么投影到屏幕上的高度就是:

height = 200* viewDist/dist

这里的viewDist是人到屏幕的距离, dist 是人到墙的距离, height是墙投影到屏幕上的高度~

因此我们需要添加一个绘制相应高度线段的函数, drawSeg(height, idx)

参数是线段的高度和线段在屏幕上的x坐标,


那么修改后的castRay函数就是:

function castRay(oriX, oriY, dirX, dirY, idx)

{

      var x = oriX*100;

      var y = oriY*100;

      var k = dirY/dirX;

      var tx = Math.ceil(x);

      var ty = k*(tx-x)+y;

      //初次交点就是tx, ty 点

      var DX = 100;

      var DY = 100*dirY/dirX;

      var dist = 0;

      while(true)

     {

            var wallX = Math.floor(tx/100);

            var wallY = Math.floor(ty/100);

           if(map[wallX][wallY] != 0)

          {

                    break;

            }

            tx += DX;

            ty += DY;

      }

      var height = 200* viewDist/dist

      drawSeg(height,  idx);

}


整体的结构就是:

draw()

{

    for(var i  = 0; i < 320; i++)//320 条光线

    {

         var dir = [100,  -(i-100)];//光线在世界中的方向 世界中的 x和y方向~ 不要和屏幕 中的方向混淆了

         //那么沿着这个方向, 考虑玩家的位置 开始发送光线吧

        castRay(player.x, player.y,  dir[0], dir[1], i);

    }

}


至于drawSeg 则和具体的canvas如何在屏幕上画一条线段相关~



后续:

这里我们只考虑了一个非常特殊的 情况, 一个静止的面向右向的玩家所看到的世界。

我们只考虑了和竖直轴相交的问题, 还要考虑和水平轴相交的问题, 处理方法类似。

这个世界看起来也非常奇怪, 这种奇特的现象叫做鱼眼效应, 因此也要想办法消除~

ok, 一切静待后续~






© 著作权归作者所有

共有 人打赏支持
李勇2

李勇2

粉丝 45
博文 188
码字总数 62209
作品 0
广州
程序员
扣丁学堂HTML5培训课程怎么样

  在如今,随着移动互联网技术的发展和进步,比如HTML5在移动互联端的应用,让更多人了解到它的丰富性趣味性便利性,但HTML5就包括这些么?扣丁学堂HTML5培训课程怎么样?零基础能学会么?...

扣丁学堂 ⋅ 06/01 ⋅ 0

超级绚丽,20款前端动画特效,轰炸你的眼睛

前言 HTML5一个相当出色的web技术,它不仅可以让你更加方便地操纵页面元素,而且可以通过canvas实现更多的动画特效,引进HTML5标准后,CSS3也就可以发挥更大的作用。本文主要介绍了一些基于H...

浪漫程序员 ⋅ 04/25 ⋅ 0

WEB前端开发学习HTML5到底有多厉害?

Web前端开发工程师是一个很新的职业,是从事Web前端开发工作的工程师。主要进行网站开发,优化,完善的工作。网页制作是Web 1.0时代的产物,那时网站的主要内容都是静态的,用户使用网站的行...

web前端小辰 ⋅ 05/23 ⋅ 0

Web前端最全面试宝典- Html篇

HTML 1.对WEB标准以及W3C的理解与认识 标签闭合、标签小写、不乱嵌套、提高搜索机器人搜索几率、使用外 链css和js脚本、结构行为表现的分离、文件下载与页面速度更快、内容能被更多的用户所访...

颜卿今天Coding了吗 ⋅ 2017/03/04 ⋅ 0

白鹭引擎 5.1.11 发布,集中修复数个 Bug

白鹭引擎5.1.11版本将于5月7日上线,本次版本的推出是对去年12月份发布的5.1版引擎的一次集中性缺陷修复。同时我们要再一次感谢多位开发者通过Egret社区等渠道提交的Bug反馈。5.1.11版本引擎...

白鹭科技 ⋅ 05/07 ⋅ 1

白鹭引擎 5.3.0 正式发布,支持 3D 游戏开发

一直关注白鹭引擎的童靴会发现,今天白鹭引擎先后完成了白鹭引擎5.2.0、白鹭引擎5.3.0两个版本的更迭,在周三发布《白鹭引擎稳定版即将发布,后续路线图同步公开》文中,我们已详细介绍了白鹭...

白鹭科技 ⋅ 05/25 ⋅ 0

HTML5学习之Web Storage基础知识

HTML5 Web 存储 在HTML5 Web Storage还没出来之前,本地存储使用的是 cookie. 但是Web 存储需要更加的安全与快速,这些数据不会被保存在服务器上,但是这些数据只用于用户请求网站数据上.它也可...

CHIEMINCHAN ⋅ 05/11 ⋅ 0

前端新人关注的Web前端饱和性分析?前端面试必知必会的十点!

现在前端市场是不是已经饱和了?巴巴巴巴巴...... 还有:XXX行业是否已经饱和? angular1.5是不是已经被淘汰? 前端还有前途吗? bootstrap为什么被称为垃圾框架?等等等 不是博主不友好,只...

web前端05 ⋅ 06/15 ⋅ 0

当~当~当~当!书架博客试运行

列表 在网页上,内容排版大多做成列表的形式。尤其是众多的博客系统,不管是 Hexo 还是 Wordpress 搭建的个人博客都是这样,这导致一个页面展示的内容及其有限。 2. 九宫格 手机上为了一个页...

dkvirus ⋅ 06/03 ⋅ 0

Phaser 3.6.0 发布,优秀的 HTML5 游戏框架

Phaser 3.6.0 已发布。Phaser 是一款非常优秀的 HTML5 游戏框架,致力于发展 PC 端和移动端的 HTML5 游戏,是一款不可多得的神器。基于 Pixi.js,支持桌面和移动 Web 浏览器。游戏可以通过第...

王练 ⋅ 04/23 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Sqoop

1.Sqoop: 《=》 SQL to Hadoop 背景 1)场景:数据在RDBMS中,我们如何使用Hive或者Hadoop来进行数据分析呢? 1) RDBMS ==> Hadoop(广义) 2) Hadoop ==> RDBMS 2)原来可以通过MapReduce I...

GordonNemo ⋅ 32分钟前 ⋅ 0

全量构建和增量构建的区别

1.全量构建每次更新时都需要更新整个数据集,增量构建只对需要更新的时间范围进行更新,所以计算量会较小。 2.全量构建查询时不需要合并不同Segment,增量构建查询时需要合并不同Segment的结...

无精疯 ⋅ 42分钟前 ⋅ 0

如何将S/4HANA系统存储的图片文件用Java程序保存到本地

我在S/4HANA的事务码MM02里为Material维护图片文件作为附件: 通过如下简单的ABAP代码即可将图片文件的二进制内容读取出来: REPORT zgos_api.DATA ls_appl_object TYPE gos_s_obj.DA...

JerryWang_SAP ⋅ 今天 ⋅ 0

云计算的选择悖论如何对待?

导读 人们都希望在工作和生活中有所选择。但心理学家的调查研究表明,在多种选项中进行选择并不一定会使人们更快乐,甚至不会产生更好的决策。心理学家Barry Schwartz称之为“选择悖论”。云...

问题终结者 ⋅ 今天 ⋅ 0

637. Average of Levels in Binary Tree - LeetCode

Question 637. Average of Levels in Binary Tree Solution 思路:定义一个map,层数作为key,value保存每层的元素个数和所有元素的和,遍历这个树,把map里面填值,遍历结束后,再遍历这个map,把每...

yysue ⋅ 今天 ⋅ 0

IDEA配置和使用

版本控制 svn IDEA版本控制工具不能使用 VCS-->Enable Version Control Integration File-->Settings-->Plugins 搜索Subversion,勾选SVN和Git插件 删除.idea文件夹重新生成项目 安装SVN客户......

bithup ⋅ 今天 ⋅ 0

PE格式第三讲扩展,VA,RVA,FA的概念

作者:IBinary 出处:http://www.cnblogs.com/iBinary/ 版权所有,欢迎保留原文链接进行转载:) 一丶VA概念 VA (virtual Address) 虚拟地址的意思 ,比如随便打开一个PE,找下它的虚拟地址 这边...

simpower ⋅ 今天 ⋅ 0

180623-SpringBoot之logback配置文件

SpringBoot配置logback 项目的日志配置属于比较常见的case了,之前接触和使用的都是Spring结合xml的方式,引入几个依赖,然后写个 logback.xml 配置文件即可,那么在SpringBoot中可以怎么做?...

小灰灰Blog ⋅ 今天 ⋅ 0

冒泡排序

原理:比较两个相邻的元素,将值大的元素交换至右端。 思路:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第...

人觉非常君 ⋅ 今天 ⋅ 0

Vagrant setup

安装软件 brew cask install virtualboxbrew cask install vagrant 创建project mkdir -p mst/vmcd mst/vmvagrant init hashicorp/precise64vagrant up hashicorp/precise64是一个box......

遥借东风 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部