“AS3.0高级动画编程”学习:第三章等角投影(上)

什么是等角投影(isometric)?

原作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com

刚接触这个概念时,我也很茫然,百度+google了N天后,找到了一些文章:

[转载]等角(斜45度)游戏与数学  ( 原文链接:http://www.javaeye.com/articles/1225

[转载]使用illustrator和正交投影原理以及基本三视图制图   (http://www.vanqy.cn/index.php/2009/03/working-with-orthographic-projections-and-basic-isometrics/)

以及这篇ppt:http://files.cnblogs.com/yjmyzz/Isometric.rar

建议先耐心看完这三篇文章,再往下看:

在之前学习的3D基础( http://www.cnblogs.com/yjmyzz/archive/2010/05/08/1730697.html )、3D线条与填充 ( http://www.cnblogs.com/yjmyzz/archive/2010/05/14/1735154.html)、背面剔除与 3D 灯光 ( http://www.cnblogs.com/yjmyzz/archive/2010/06/06/1752674.html)中,我们所采用的3D坐标系,基本上都属于3D透视投影坐标。通俗点讲:就是物体距离观察者越远,看上去就越小,最终消失在远处的某个点,也可以俗称为“带消失点的3D投影”。这种投影方法虽然精确,但是动画编程中严格按照它来处理,开销很大,计算量也很大,因为不同的z轴距离,或者距离观察点的位置不一样,物体的大小就要调整(如果考虑到光源等因素,处理量就更大了)。

而等角投影中,没有消失点,观察者的目光始终是平行的,投影方向与坐标轴的角度是固定值,虽然这样看上去略有失真,但是总体来讲立体感还是很明显的,重要的是:不管你把等角投影所形成的立体图形放在屏幕上哪一个位置,看上去都是相同的。

原书作者还给出了一个演示,用于帮助大家理解:在线演示

很明显:一个立方体的(正方形)顶部面,在经过等角投影后,在屏幕上会发生形变,成为一个菱形。(点击刚才的在线演示中的true isometric按钮,观察front视图中立方体的顶部)

上图是正方形经过标准等角投影后得到的菱形,其左右侧的角度为60度,通过计算可以得到长宽比例为1.73,但是这个比例通常在计算时,会弄出很多小数位,而且绘图师们也比较烦这个比例(因为用ps等软件画图时,同样也要设置长或宽为小数位才能保证这个比例)

所以在实际情况中,更常用的是"二等角"来代替"等角"(点击刚才的在线演示中的dimetric按钮,观察front视图中立方体的顶部)

可以看出,“二等角投影”形成的菱形要比“等角投影”更扁一些,但这种图形的宽/高比例正好是2,处理起来很方便,也好记忆。

有了上面这些基础,就可以来做些正经事儿了,思考一个问题:在常规3D空间中的图形,经过二等角投影(为方便起见,以下把二等角投影也通称为等角投影)后,要经过怎样的计算(或转换),才能得到最终的图形呢?

有鉴于任何几何图形,总是由若干个点连接而成的,我们先来定义一个常规的Point3D类:

 1 package {
 2     public class Point3D {
 3         public var x:Number;
 4         public var y:Number;
 5         public var z:Number;
 6         public function Point3D(x:Number=0,y:Number=0,z:Number=0) {
 7             this.x=x;
 8             this.y=y;
 9             this.z=z;
10         }
11     }
12 }

所以上面的问题也可以简化为:等角空间中3D坐标点,如何转换为电脑屏幕上的2D坐标点?(或者反过来转换?)

转化公式:
x1 = x - z
y1 = y * 1.2247 + (x + z) * 0.5
z2 = (x + z) * 0.866 - y * 0.707 --用于层深排序,可以先不管

上面的公式可以把等角空间中的坐标点,转化为屏幕空间上的坐标点。(好奇心强烈的童鞋们,自己去看原书上的推导过程吧,我建议大家把这它当成定理公式记住就好,毕竟我们不是在研究数学)

为了方便以后重用,可以把这个公式封装到类IsoUtil.as里

 1 package {
 2
 3     import flash.geom.Point;
 4     public class IsoUtils {
 5
 6         //public static const Y_CORRECT:Number=Math.cos(- Math.PI/6)*Math.SQRT2;
 7         public static const Y_CORRECT:Number = 1.2247448713915892;
 8
 9         //把等角空间中的一个3D坐标点转换成屏幕上的2D坐标点
10         public static function isoToScreen(pos:Point3D):Point {
11             var screenX:Number=pos.x-pos.z;
12             var screenY:Number=pos.y*Y_CORRECT+(pos.x+pos.z)*0.5;
13             return new Point(screenX,screenY);
14         }
15
16         //把屏幕上的2D坐标点转换成等角空间中的一个3D坐标点
17         public static function screenToIso(point:Point):Point3D {
18             var xpos:Number=point.y+point.x*.5;
19             var ypos:Number=0;
20             var zpos:Number=point.y-point.x*.5;
21             return new Point3D(xpos,ypos,zpos);
22         }
23     }
24 }

用代码来画一个等角图形,测试上面的代码是否正确

 1 package {
 2
 3     import flash.display.Sprite;
 4     import flash.display.StageAlign;
 5     import flash.display.StageScaleMode;
 6     import flash.geom.Point;
 7
 8     [SWF(backgroundColor=0xefefef,height="200",width="300")]
 9     public class IsoTransformTest extends Sprite {
10         public function IsoTransformTest() {
11             stage.align=StageAlign.TOP_LEFT;
12             stage.scaleMode=StageScaleMode.NO_SCALE;
13
14             var p0:Point3D=new Point3D(0,0,0);
15             var p1:Point3D=new Point3D(100,0,0);
16             var p2:Point3D=new Point3D(100,0,100);
17             var p3:Point3D=new Point3D(0,0,100);
18
19             var sp0:Point=IsoUtils.isoToScreen(p0);
20             var sp1:Point=IsoUtils.isoToScreen(p1);
21             var sp2:Point=IsoUtils.isoToScreen(p2);
22             var sp3:Point=IsoUtils.isoToScreen(p3);
23
24             var tile:Sprite = new Sprite();
25             tile.x=150;
26             tile.y=50;
27             addChild(tile);
28
29             tile.graphics.lineStyle(0);
30             tile.graphics.moveTo(sp0.x, sp0.y);
31             tile.graphics.lineTo(sp1.x, sp1.y);
32             tile.graphics.lineTo(sp2.x, sp2.y);
33             tile.graphics.lineTo(sp3.x, sp3.y);
34             tile.graphics.lineTo(sp0.x, sp0.y);
35
36             trace(Math.cos(- Math.PI/6)*Math.SQRT2);//1.2247448713915892
37             trace(tile.width,tile.height);//200 100 符合上面提到的2:1
38         }
39     }
40 }

正如在OO世界里,很多语言都习惯于弄一个Object基类做为祖先一样,在等角世界里,我们也可以弄一个IsoObject的基类,把坐标变换这一套东西封装在里面,方便重用

 1 package {
 2     import flash.display.Sprite;
 3     import flash.geom.Point;
 4     import flash.geom.Rectangle;
 5
 6     public class IsoObject extends Sprite {
 7
 8         protected var _position:Point3D;
 9         protected var _size:Number;
10         protected var _walkable:Boolean=false;
11
12         //public static const Y_CORRECT:Number=Math.cos(- Math.PI/6)*Math.SQRT2;
13         public static const Y_CORRECT:Number=1.2247448713915892;
14
15         public function IsoObject(size:Number) {
16             _size=size;
17             _position = new Point3D();
18             updateScreenPosition();
19         }
20
21         //更新屏幕坐标位置
22         protected function updateScreenPosition():void {
23             var screenPos:Point=IsoUtils.isoToScreen(_position);
24             super.x=screenPos.x;
25             super.y=screenPos.y;
26         }
27
28         override public function toString():String {
29             return "[IsoObject (x:" + _position.x + ", y:" + _position.y+ ", z:" + _position.z + ")]";
30         }
31
32         //设置等角空间3D坐标点的x,y,z值
33         override public function set x(value:Number):void {
34             _position.x=value;
35             updateScreenPosition();
36         }
37
38         override public function get x():Number {
39             return _position.x;
40         }
41
42         override public function set y(value:Number):void {
43             _position.y=value;
44             updateScreenPosition();
45         }
46         override public function get y():Number {
47             return _position.y;
48         }
49
50         override public function set z(value:Number):void {
51             _position.z=value;
52             updateScreenPosition();
53         }
54
55         override public function get z():Number {
56             return _position.z;
57         }
58
59         //_position的属性封装
60         public function set position(value:Point3D):void {
61             _position=value;
62             updateScreenPosition();
63         }
64         public function get position():Point3D {
65             return _position;
66         }
67
68         //深度排序时会用到,现在不用理这个
69         public function get depth():Number {
70             return (_position.x + _position.z) * .866 - _position.y * .707;
71         }
72
73         //这个暂时也不用理
74         public function set walkable(value:Boolean):void {
75             _walkable=value;
76         }
77         public function get walkable():Boolean {
78             return _walkable;
79         }
80
81         public function get size():Number {
82             return _size;
83         }
84
85         public function get rect():Rectangle {
86             return new Rectangle(x - size / 2, z - size / 2, size, size);
87         }
88     }
89 }

接触过3D渲染或动画的朋友们也许都知道,通常人们习惯弄出一个最基本的三角形(或其它小形状)做为基本贴片,用这些小贴片最终构成复杂的3D模型,类似的,我们也可以做一个基本的IsoTile贴片类

 1 package {
 2     public class DrawnIsoTile extends IsoObject {
 3         protected var _height:Number;
 4         protected var _color:uint;
 5         public function DrawnIsoTile(size:Number,color:uint,height:Number=0) {
 6             super(size);
 7             _color=color;
 8             _height=height;
 9             draw();
10         }
11
12         //画矩形"贴片"
13         protected function draw():void {
14             graphics.clear();
15             graphics.beginFill(_color);
16             graphics.lineStyle(0,0,.5);
17             graphics.moveTo(- size,0);
18             graphics.lineTo(0,- size*.5);
19             graphics.lineTo(size,0);
20             graphics.lineTo(0,size*.5);
21             graphics.lineTo(- size,0);
22         }
23
24         //height属性暂时不用管(在draw里也没用到)
25         override public function set height(value:Number):void {
26             _height=value;
27             draw();
28         }
29         override public function get height():Number {
30             return _height;
31         }
32
33         //设置颜色
34         public function set color(value:uint):void {
35             _color=value;
36             draw();
37         }
38         public function get color():uint {
39             return _color;
40         }
41     }
42 }

试一下IsoTile:

可以把这个当做游戏中的空白地图,ok,继续,光画一个平面,也许没什么意思,再考虑更复杂一些的物体:比如(立方体)盒子(其实基本思路不复杂,把贴片提高一些位置,然后向下伸出同等长度的线条,最终连接起来即可)。

在等角世界中,站在观察者的角度,一个立方体最终能看到的只有三个面(top,left,right),如果再加个光源的话,还应该体现出颜色的明暗度差别(比如如果一个光源从盒子的右上方照过来,上方应该是最亮的,右侧其次,左侧最暗)

 1 package {
 2     public class DrawnIsoBox extends DrawnIsoTile {
 3         public function DrawnIsoBox(size:Number, color:uint, height:Number) {
 4             super(size, color, height);
 5         }
 6         override protected function draw():void {
 7             graphics.clear();
 8
 9             //提取r,g,b三色分量
10             var red:int=_color>>16;
11             var green:int=_color>>8&0xff;
12             var blue:int=_color&0xff;
13
14             //假如光源在右上方(所以左侧最暗,顶上最亮,右侧在二者之间)
15             var leftShadow:uint = (red * .5) << 16 |(green * .5) << 8 |(blue * .5);
16             var rightShadow:uint = (red * .75) << 16 |(green * .75) << 8 | (blue * .75);
17             var h:Number=_height*Y_CORRECT;
18
19             //顶部
20             graphics.beginFill(_color);
21             graphics.lineStyle(0, 0, .5);
22             graphics.moveTo(-_size, -h);
23             graphics.lineTo(0, -_size * .5 - h);
24             graphics.lineTo(_size, -h);
25             graphics.lineTo(0, _size * .5 - h);
26             graphics.lineTo(-_size, -h);
27             graphics.endFill();
28
29             //左侧
30             graphics.beginFill(leftShadow);
31             graphics.lineStyle(0, 0, .5);
32             graphics.moveTo(-_size, -h);
33             graphics.lineTo(0, _size * .5 - h);
34             graphics.lineTo(0, _size * .5);
35             graphics.lineTo(-_size, 0);
36             graphics.lineTo(-_size, -h);
37             graphics.endFill();
38
39             //右侧
40             graphics.beginFill(rightShadow);
41             graphics.lineStyle(0, 0, .5);
42             graphics.moveTo(_size, -h);
43             graphics.lineTo(0, _size * .5 - h);
44             graphics.lineTo(0, _size * .5);
45             graphics.lineTo(_size, 0);
46             graphics.lineTo(_size, -h);
47             graphics.endFill();
48         }
49     }
50 }

测试一下IsoBox

 1 package {
 2
 3
 4     import flash.display.Sprite;
 5     import flash.display.StageAlign;
 6     import flash.display.StageScaleMode;
 7     import flash.events.*;
 8     import flash.events.MouseEvent;
 9     import flash.geom.Point;
10     [SWF(backgroundColor=0xffffff,height=380,width=600)]
11     public class BoxTest extends Sprite
12     {
13         private var world:Sprite;
14
15         public function BoxTest()
16         {
17             stage.align = StageAlign.TOP_LEFT;
18             stage.scaleMode = StageScaleMode.NO_SCALE;
19             world = new Sprite();
20             world.x = stage.stageWidth / 2;
21             world.y = 50;
22             addChild(world);
23             for(var i:int = 0; i < 15; i++)
24             {
25                 for(var j:int = 0; j < 15; j++)
26                 {
27                     var tile:DrawnIsoTile = new DrawnIsoTile(20, 0xcccccc);
28                     tile.position = new Point3D(i * 20, 0, j * 20);
29                     world.addChild(tile);
30                 }
31             }
32             world.addEventListener(MouseEvent.CLICK, onWorldClick);
33             stage.addEventListener(Event.RESIZE,resizeHandler);
34         }
35
36         private function onWorldClick(event:MouseEvent):void
37         {
38             var box:DrawnIsoBox = new DrawnIsoBox(20, Math.random() * 0xffffff, 20);
39             var pos:Point3D = IsoUtils.screenToIso(new Point(world.mouseX, world.mouseY));
40             pos.x = Math.round(pos.x / 20) * 20;
41             pos.y = Math.round(pos.y / 20) * 20;
42             pos.z = Math.round(pos.z / 20) * 20;
43             box.position = pos;
44             world.addChild(box);
45         }
46
47         private function resizeHandler(e:Event):void{
48             world.x = stage.stageWidth / 2;
49         }
50     }
51 }

在线演示

稍加解释,上面这段代码先画一个空白地图,然后在地图上注册鼠标点击事件,每次点击将在地图上生成一个IsoBox实例

时间: 2024-08-26 14:54:38

“AS3.0高级动画编程”学习:第三章等角投影(上)的相关文章

“AS3.0高级动画编程”学习:第二章转向行为(下)

在上一篇里,我们学习了“自主角色”的一些基本行为:寻找(seek).避开(flee).到达(arrive).追捕(pursue).躲避(evade).漫游(wander).这一篇将继续学习其它更复杂,更高级的行为. 原作者:菩提树下的杨过出处:http://yjmyzz.cnblogs.com 一.对象回避(object avoidance) 对象回避的正式解释为:角色预测出对象的行动路径,然后避开他们. 也可以通俗的描述为:假如有一个"灰太狼抓喜羊羊"的游戏场景,“喜羊羊"

“AS3.0高级动画编程”学习:第四章 寻路(AStar/A星/A*)算法 (上)

“AS3.0高级动画编程”学习:第四章 寻路(AStar/A星/A*)算法 (上) 原作者:菩提树下的杨过出处:http://yjmyzz.cnblogs.com 一提到“A*算法”,可能很多人都有"如雷贯耳"的感觉.用最白话的语言来讲:把游戏中的某个角色放在一个网格环境中,并给定一个目标点和一些障碍物,如何让角色快速“绕过障碍物”找出通往目标点的路径.(如下图) 在寻路过程中,角色总是不停从一个格子移动到另一个相邻的格子,如果单纯从距离上讲,移动到与自身斜对角的格子走的距离要长一些,

Windows API 编程学习记录&lt;三&gt;

恩,开始写API编程的第三节,其实马上要考试了,但是不把这节写完,心里总感觉不舒服啊.写完赶紧去复习啊       在前两节中,我们介绍了Windows API 编程的一些基本概念和一个最基本API函数 MessageBox的使用,在这节中,我们就来正式编写一个Windows的窗口程序. 在具体编写代码之前,我们必须先要了解一下API 编写窗口程序具体的三个基本步骤:             1. 注册窗口类:             2.创建窗口:             3.显示窗口: 恩,

《javascript高级程序设计》第二、三章知识点整理

第二章知识点总结 1.<script>在html中的使用 主要功能:在页面中嵌入javascript代码或包含外部javascript文件. 常用属性: type:用于定义脚步代码的语言类型,默认为text/javascript. src:包含外部域的javascript文件. defer:表示脚本会在整个页面加载完毕之后运行,只对外部文件有效,最好只包含一个延迟脚本. async:脚本在不妨碍其他操作的情况下立即下载(不保证下载文件的顺序). 插入位置:在页面<body>标签页面

oracle学习 第三章 常用的SQL*PLUS命令 ——02

今天接着昨天的RUN命令继续讲. 3.5 n(设置当前行)命令和A(PPEND)(附加)命令 设想,你输入了例3-10的查询语句 例 3-10 SQL> SELECT ename 2 FROM emp; 例 3-10 结果 看到以上输出时,您发现在SELECT子句中忘了job,sal.这时您又如何修改您的SELECT子句呢?首先您应该使用SQL*PLUS的L(LIST)命令来显示SQL缓冲中的内容. 例 3-11 SQL> L 例 3-11 结果 在例3-11显示的结果中,2后面的"

安卓学习——第三章

安卓学习——第三章 由于自身的实力不足,经过尝试后放弃了自己编写记账本app,而是打算找到合适的相关项目进行学习.我找到了几个AndroidStudio项目,我在AndroidStudio里面直接打开文件,发现无法运行.想到应该是import项目才行(我可真是菜的真实). 通过import这些项目之后,发现其中一个项目竟然是可以跑起来的.我便将项目通过手机进行测试,发现在手机上也是可以运行的.不过有一些在外观上的问题. 首先是在手机上的显示,会有一部分文字内容是乱码.我便去找编译器里面相关文件的

Android 5.0+高级动画开发 矢量图动画 轨迹动画 路径变换

第1章 课程介绍为了成就更多高逼格的人才,我专门整理了Android5.0以后主推的实现酷炫动画的新技术,教你掌握实现动画的高逼格技巧.课程中我会详细讲解每个动画效果实现的原理和所用的技术,并带你一步一步的实现每个动画效果,让你在学完本次课程后,能够举一反三,再也不必担心设计MM的设计你没法实现了,也再也不用担心,老板的脑洞无... 第2章 矢量图VectorDrawable打造新时代酷炫动画本章讲解Android5.0以后主推的技术之一-矢量图VectorDrawable在Android中的使

Python编程入门-第三章 编写程序 -学习笔记

第三章 编写程序 1.编辑源程序.运行程序 可通过IDLE中File>New File新建一个文本以编辑源程序,编辑完成可通过Run>Run Module(或者F5快捷键)来运行程序.Python源文件都以.py格式存储. 2.从命令行运行程序 除了上述利用IDLE的集成功能运行程序的方式外,当然也可以通过命令行运行程序,命令格式为:python ‘源文件名称.py’. 3.编译源代码 当运行py格式文件时,Python会自动创建相应的.pyc文件,该文件包含编译后的代码即目标代码,目标代码基

Struts2框架学习第三章——Struts2基础

本章要点 —  Struts 1框架的基本知识 — 使用Struts 1框架开发Web应用 —  WebWork框架的基本知识 — 使用WebWork框架开发Web应用 — 在Eclipse中整合Tomcat — 使用Eclipse开发Web应用 — 为Web应用增加Struts 2支持 —  Struts 2框架的MVC组件 —  Struts 2框架的流程 — 通过web.xml文件加载Struts 2框架 — 通过struts.properties文件配置Struts 2属性 —  str