3D场景交互技术:从对象操作到相机控制
1. 3D对象拖动交互优化
在3D场景中,拖动对象是常见的交互需求。最初的代码中,对
_rook
对象添加了
MOUSE_DOWN
事件监听,对
_board
对象添加了
MOUSE_UP
事件监听,并设置
_rook
使用手型光标。
super();
_rook.addEventListener(MouseEvent3D.MOUSE_DOWN, _onMouseDown);
_board.addEventListener(MouseEvent3D.MOUSE_UP, _onMouseUp);
_rook.useHandCursor = true;
当鼠标释放时,需要在
_onMouseUp
方法末尾添加代码来恢复之前的修改:
_rook.mouseEnabled = true;
_board.useHandCursor = false;
重新编译
DraggingObjectsInSpace
示例后,拖动
rook
对象时会有更流畅、响应更灵敏的交互效果。
2. 使用MouseEvent3D的UV坐标进行纹理绘制
2.1 UV坐标简介
UV坐标表示纹理空间中的二维位置向量,取值范围在0到1之间。(0, 0)对应材质
BitmapData
对象的左下角,(1, 1)对应右上角。当Away3D广播3D鼠标事件时,如果广播对象的材质使用了UV坐标,会计算鼠标光标下的精确UV值。
2.2 实现纹理绘制工具
创建一个继承自
Chapter08SampleBase
的类
PaintingOnObjects
,实现棋盘表面的纹理绘制工具。
package flash3dbook.ch08
{
import away3d.events.*;
import away3d.materials.*;
import flash3dbook.ch08.*
import flash.display.*;
import flash.geom.*;
[SWF(width="800", height="600")]
public class PaintingOnObjects extends Chapter08SampleBase
{
private var _brush : Shape;
public function PaintingOnObjects()
{
super();
_brush = new Shape();
_brush.graphics.beginFill(0xff0000, 0.5);
_brush.graphics.drawEllipse(-10, -10, 20, 20);
_board.useHandCursor = true;
_board.addEventListener(MouseEvent3D.MOUSE_DOWN, _onMouseDown);
_board.addEventListener(MouseEvent3D.MOUSE_UP, _onMouseUp);
}
protected function _onMouseDown(ev : MouseEvent3D) : void
{
_board.addEventListener(MouseEvent3D.MOUSE_MOVE, _onMouseMove);
}
protected function _onMouseMove(ev : MouseEvent3D) : void
{
}
protected function _onMouseUp(ev : MouseEvent3D) : void
{
_board.removeEventListener(MouseEvent3D.MOUSE_MOVE, _onMouseMove);
}
}
}
在上述代码中,3D鼠标事件的监听设置与之前的示例类似,不同之处在于所有3D鼠标事件监听器都设置在
_board
对象上。同时,在构造函数中创建了一个名为
_brush
的
Shape
对象,它是一个半透明的红色圆形,将作为画笔用于绘制棋盘材质的
BitmapData
对象。
2.3 完善绘制功能
在
_onMouseMove
方法中添加代码来完成绘制功能:
var mat : WhiteShadingBitmapMaterial = _board.material as WhiteShadingBitmapMaterial;
var bmp : BitmapData = mat.bitmap;
var mtx : Matrix = new Matrix();
mtx.tx = ev.uv.u * bmp.width;
mtx.ty = (1 - ev.uv.v) * bmp.height;
bmp.draw(_brush, mtx);
mat.bitmap = bmp;
这里,首先获取棋盘的材质对象和对应的
BitmapData
对象,然后创建一个
Matrix
对象来定位画笔。通过计算UV坐标对应的像素位置,将画笔绘制到
BitmapData
对象上。最后,重置材质的
bitmap
属性,标记材质需要重新绘制。
2.4 另一种标记材质重绘的方法
Away3D使用
arcane
命名空间,其中有一系列布尔属性可临时标记对象为“脏”(需要重绘)。可以使用
_materialDirty
属性来标记材质重绘:
import flash3dbook.ch08.*;
// 假设mat是材质对象
mat._materialDirty = true;
这种方法可以避免额外的
bmp
变量,使代码更简洁。
3. 第一人称相机键盘控制
3.1 键盘行走控制
在许多第一人称游戏和应用中,使用W、A、S、D键或箭头键来控制相机的前后左右移动。为了实现这种键盘交互,我们将跟踪每个键的状态(按下或释放),并在场景更新时根据键的状态移动相机。
3.2 实现步骤
-
创建一个继承自
Chapter08SampleBase的类FirstPersonCamera:
package flash3dbook.ch08
{
import flash.events.*;
import flash.ui.*;
import flash3dbook.ch08.*;
[SWF(width="800", height="600")]
public class FirstPersonCamera extends Chapter08SampleBase
{
public function FirstPersonCamera()
{
super();
}
protected override function _onEnterFrame(ev : Event) : void
{
_view.render();
}
}
}
- 添加键状态变量:
protected var _keyUp : Boolean = false;
protected var _keyDown : Boolean = false;
protected var _keyLeft : Boolean = false;
protected var _keyRight : Boolean = false;
- 创建键盘事件处理方法:
private function _onKeyDown(ev : KeyboardEvent) : void
{
if (ev.charCode == 119 || ev.keyCode == Keyboard.UP) {
_keyUp = true;
}
else if (ev.charCode == 97 || ev.keyCode == Keyboard.LEFT) {
_keyLeft = true;
}
else if (ev.charCode == 115 || ev.keyCode == Keyboard.DOWN) {
_keyDown = true;
}
else if (ev.charCode == 100 || ev.keyCode == Keyboard.RIGHT) {
_keyRight = true;
}
}
private function _onKeyUp(ev : KeyboardEvent) : void
{
if (ev.charCode == 119 || ev.keyCode == Keyboard.UP) {
_keyUp = false;
}
else if (ev.charCode == 97 || ev.keyCode == Keyboard.LEFT) {
_keyLeft = false;
}
else if (ev.charCode == 115 || ev.keyCode == Keyboard.DOWN) {
_keyDown = false;
}
else if (ev.charCode == 100 || ev.keyCode == Keyboard.RIGHT) {
_keyRight = false;
}
}
- 在构造函数中添加事件监听:
stage.addEventListener(KeyboardEvent.KEY_DOWN, _onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, _onKeyUp);
-
在
_onEnterFrame方法中添加相机移动代码:
// Move forward or backward
if (_keyUp) {
_view.camera.moveForward(15*Math.cos(_view.camera.rotationX*Math.PI/180));
_view.camera.moveUp(-15*Math.sin(_view.camera.rotationX*Math.PI/180));
} else if (_keyDown) {
_view.camera.moveBackward(15*Math.cos(_view.camera.rotationX*Math.PI/180));
_view.camera.moveDown(-15*Math.sin(_view.camera.rotationX*Math.PI/180));
}
// Turn left/right
if (_keyLeft)
_view.camera.rotationY -= 3;
else if (_keyRight)
_view.camera.rotationY += 3;
编译
FirstPersonCamera
示例后,就可以使用箭头键或W、A、S、D键在棋盘周围导航。
3.3 键盘控制相机移动流程
graph LR
A[用户按键] --> B{按键类型}
B -->|W或上箭头| C(_keyUp = true)
B -->|A或左箭头| D(_keyLeft = true)
B -->|S或下箭头| E(_keyDown = true)
B -->|D或右箭头| F(_keyRight = true)
C --> G{场景更新}
D --> G
E --> G
F --> G
G --> H{_keyUp是否为true}
H -->|是| I[相机向前移动]
H -->|否| J{_keyDown是否为true}
J -->|是| K[相机向后移动]
J -->|否| L{_keyLeft是否为true}
L -->|是| M[相机左转]
L -->|否| N{_keyRight是否为true}
N -->|是| O[相机右转]
4. 鼠标拖动环顾场景
4.1 问题与解决方案
在Flash中,只能获取鼠标光标在屏幕上的位置,且通常只有光标在Flash窗口内时才有信息。为了解决这个问题,可以使用拖动交互,即只有当用户按住鼠标按钮并拖动光标时才旋转相机。
4.2 实现步骤
-
创建一个继承自
FirstPersonCamera的类FirstPersonCameraWithDrag:
package flash3dbook.ch08
{
import flash.events.*;
import flash.geom.*;
import flash3dbook.ch08.*;
[SWF(width="800", height="600")]
public class FirstPersonCameraWithDrag extends FirstPersonCamera
{
private var _lastPoint : Point = new Point();
public function FirstPersonCameraWithDrag()
{
super();
stage.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
}
private function _onMouseDown(ev : MouseEvent) : void
{
_lastPoint.x = stage.mouseX;
_lastPoint.y = stage.mouseY;
stage.addEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove);
stage.addEventListener(Event.MOUSE_LEAVE, _onMouseLeave);
}
private function _onMouseMove(ev : MouseEvent) : void
{
}
private function _onMouseUp(ev : MouseEvent) : void
{
stage.removeEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove);
stage.removeEventListener(Event.MOUSE_LEAVE, _onMouseLeave);
}
private function _onMouseLeave(ev : Event) : void
{
stage.removeEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove);
stage.removeEventListener(Event.MOUSE_LEAVE, _onMouseLeave);
}
}
}
-
在
_onMouseMove方法中添加相机旋转代码:
_view.camera.rotationX -= (stage.mouseY - _lastPoint.y) / 5;
_view.camera.rotationY += (stage.mouseX - _lastPoint.x) / 5;
_lastPoint.x = stage.mouseX;
_lastPoint.y = stage.mouseY;
这里,根据鼠标移动的距离计算相机的旋转角度,并更新
_lastPoint
变量。
4.3 鼠标拖动旋转相机流程
graph LR
A[鼠标按下] --> B[记录当前鼠标位置]
B --> C[监听MOUSE_MOVE事件]
C --> D{鼠标移动}
D --> E[计算鼠标移动距离]
E --> F[根据移动距离旋转相机]
F --> G[更新_lastPoint变量]
G --> D
D --> H{鼠标释放}
H -->|是| I[停止监听MOUSE_MOVE事件]
通过以上步骤,我们实现了3D场景中对象的拖动交互、纹理绘制、第一人称相机的键盘控制和鼠标拖动环顾功能。这些技术可以为3D应用和游戏带来更丰富的交互体验。
5. 综合交互总结
5.1 不同交互方式对比
| 交互方式 | 实现原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 3D对象拖动交互 |
监听
MOUSE_DOWN
和
MOUSE_UP
事件,在鼠标释放时恢复相关设置
| 交互流畅、响应灵敏 | 代码相对复杂,涉及多个对象的事件监听 | 需要拖动3D对象的场景,如模型展示 |
| UV坐标纹理绘制 |
利用
MouseEvent3D
的UV坐标,计算鼠标位置并绘制到材质的
BitmapData
上
| 可以直接在3D对象表面绘制,效果直观 | 需要处理UV坐标与像素坐标的转换,可能影响性能 | 需要在3D对象表面进行绘制的场景,如地图标记 |
| 第一人称相机键盘控制 | 跟踪键盘按键状态,在场景更新时根据按键状态移动相机 | 操作简单,符合常见游戏操作习惯 | 只能进行前后左右移动和旋转,交互方式有限 | 第一人称视角的游戏或应用,如迷宫游戏 |
| 鼠标拖动环顾场景 | 监听鼠标拖动事件,根据鼠标移动距离旋转相机 | 可以突破鼠标位置限制,实现连续旋转 | 需要处理鼠标拖动逻辑,可能存在旋转不精确的问题 | 需要环顾场景的第一人称应用,如虚拟漫游 |
5.2 交互技术的组合应用
在实际的3D应用中,可以将这些交互技术组合使用,以实现更丰富的用户体验。例如,在一个第一人称的3D游戏中,可以同时使用键盘控制相机的移动和鼠标拖动环顾场景,还可以在游戏场景中的特定对象上使用UV坐标纹理绘制来标记任务目标。
6. 性能优化建议
6.1 材质重绘优化
在使用UV坐标纹理绘制时,频繁更新材质的
BitmapData
对象可能会影响性能。可以使用
arcane
命名空间的
_materialDirty
属性来标记材质需要重绘,避免创建额外的变量,减少内存开销。
6.2 事件监听优化
在监听事件时,要确保在不需要监听时及时移除事件监听器,避免不必要的事件处理,提高性能。例如,在鼠标释放时,及时移除
MOUSE_MOVE
事件监听器。
6.3 相机移动优化
在使用键盘控制相机移动时,可以根据场景的复杂程度调整相机的移动速度和旋转角度,避免过度移动和旋转导致的性能问题。
7. 未来发展趋势
7.1 多设备交互
随着移动设备和虚拟现实设备的普及,未来的3D交互技术将支持更多类型的设备,如触摸屏、手柄、VR头盔等。用户可以通过不同的设备进行更自然、更便捷的交互。
7.2 手势识别与语音交互
手势识别和语音交互技术的发展将为3D交互带来新的可能性。用户可以通过手势和语音指令来控制3D场景中的对象,实现更加直观和高效的交互。
7.3 增强现实与混合现实
增强现实(AR)和混合现实(MR)技术将虚拟对象与现实世界相结合,为3D交互带来全新的体验。用户可以在现实环境中与虚拟对象进行交互,创造出更加沉浸式的场景。
8. 总结
本文介绍了3D场景中多种交互技术,包括3D对象拖动交互、UV坐标纹理绘制、第一人称相机键盘控制和鼠标拖动环顾场景。通过详细的代码示例和流程图,展示了这些技术的实现步骤和原理。同时,还提供了性能优化建议和对未来发展趋势的展望。这些交互技术可以为3D应用和游戏带来更丰富的用户体验,开发者可以根据具体需求选择合适的交互方式,并将它们组合使用,以创造出更加出色的3D作品。
9. 代码示例汇总
以下是本文中涉及的主要代码示例汇总,方便开发者参考:
3D对象拖动交互
super();
_rook.addEventListener(MouseEvent3D.MOUSE_DOWN, _onMouseDown);
_board.addEventListener(MouseEvent3D.MOUSE_UP, _onMouseUp);
_rook.useHandCursor = true;
// _onMouseUp方法末尾添加
_rook.mouseEnabled = true;
_board.useHandCursor = false;
UV坐标纹理绘制
// PaintingOnObjects类
package flash3dbook.ch08
{
import away3d.events.*;
import away3d.materials.*;
import flash3dbook.ch08.*
import flash.display.*;
import flash.geom.*;
[SWF(width="800", height="600")]
public class PaintingOnObjects extends Chapter08SampleBase
{
private var _brush : Shape;
public function PaintingOnObjects()
{
super();
_brush = new Shape();
_brush.graphics.beginFill(0xff0000, 0.5);
_brush.graphics.drawEllipse(-10, -10, 20, 20);
_board.useHandCursor = true;
_board.addEventListener(MouseEvent3D.MOUSE_DOWN, _onMouseDown);
_board.addEventListener(MouseEvent3D.MOUSE_UP, _onMouseUp);
}
protected function _onMouseDown(ev : MouseEvent3D) : void
{
_board.addEventListener(MouseEvent3D.MOUSE_MOVE, _onMouseMove);
}
protected function _onMouseMove(ev : MouseEvent3D) : void
{
var mat : WhiteShadingBitmapMaterial = _board.material as WhiteShadingBitmapMaterial;
var bmp : BitmapData = mat.bitmap;
var mtx : Matrix = new Matrix();
mtx.tx = ev.uv.u * bmp.width;
mtx.ty = (1 - ev.uv.v) * bmp.height;
bmp.draw(_brush, mtx);
mat.bitmap = bmp;
}
protected function _onMouseUp(ev : MouseEvent3D) : void
{
_board.removeEventListener(MouseEvent3D.MOUSE_MOVE, _onMouseMove);
}
}
}
第一人称相机键盘控制
// FirstPersonCamera类
package flash3dbook.ch08
{
import flash.events.*;
import flash.ui.*;
import flash3dbook.ch08.*;
[SWF(width="800", height="600")]
public class FirstPersonCamera extends Chapter08SampleBase
{
protected var _keyUp : Boolean = false;
protected var _keyDown : Boolean = false;
protected var _keyLeft : Boolean = false;
protected var _keyRight : Boolean = false;
public function FirstPersonCamera()
{
super();
stage.addEventListener(KeyboardEvent.KEY_DOWN, _onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, _onKeyUp);
}
private function _onKeyDown(ev : KeyboardEvent) : void
{
if (ev.charCode == 119 || ev.keyCode == Keyboard.UP) {
_keyUp = true;
}
else if (ev.charCode == 97 || ev.keyCode == Keyboard.LEFT) {
_keyLeft = true;
}
else if (ev.charCode == 115 || ev.keyCode == Keyboard.DOWN) {
_keyDown = true;
}
else if (ev.charCode == 100 || ev.keyCode == Keyboard.RIGHT) {
_keyRight = true;
}
}
private function _onKeyUp(ev : KeyboardEvent) : void
{
if (ev.charCode == 119 || ev.keyCode == Keyboard.UP) {
_keyUp = false;
}
else if (ev.charCode == 97 || ev.keyCode == Keyboard.LEFT) {
_keyLeft = false;
}
else if (ev.charCode == 115 || ev.keyCode == Keyboard.DOWN) {
_keyDown = false;
}
else if (ev.charCode == 100 || ev.keyCode == Keyboard.RIGHT) {
_keyRight = false;
}
}
protected override function _onEnterFrame(ev : Event) : void
{
// Move forward or backward
if (_keyUp) {
_view.camera.moveForward(15*Math.cos(_view.camera.rotationX*Math.PI/180));
_view.camera.moveUp(-15*Math.sin(_view.camera.rotationX*Math.PI/180));
} else if (_keyDown) {
_view.camera.moveBackward(15*Math.cos(_view.camera.rotationX*Math.PI/180));
_view.camera.moveDown(-15*Math.sin(_view.camera.rotationX*Math.PI/180));
}
// Turn left/right
if (_keyLeft)
_view.camera.rotationY -= 3;
else if (_keyRight)
_view.camera.rotationY += 3;
_view.render();
}
}
}
鼠标拖动环顾场景
// FirstPersonCameraWithDrag类
package flash3dbook.ch08
{
import flash.events.*;
import flash.geom.*;
import flash3dbook.ch08.*;
[SWF(width="800", height="600")]
public class FirstPersonCameraWithDrag extends FirstPersonCamera
{
private var _lastPoint : Point = new Point();
public function FirstPersonCameraWithDrag()
{
super();
stage.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
}
private function _onMouseDown(ev : MouseEvent) : void
{
_lastPoint.x = stage.mouseX;
_lastPoint.y = stage.mouseY;
stage.addEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove);
stage.addEventListener(Event.MOUSE_LEAVE, _onMouseLeave);
}
private function _onMouseMove(ev : MouseEvent) : void
{
_view.camera.rotationX -= (stage.mouseY - _lastPoint.y) / 5;
_view.camera.rotationY += (stage.mouseX - _lastPoint.x) / 5;
_lastPoint.x = stage.mouseX;
_lastPoint.y = stage.mouseY;
}
private function _onMouseUp(ev : MouseEvent) : void
{
stage.removeEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove);
stage.removeEventListener(Event.MOUSE_LEAVE, _onMouseLeave);
}
private function _onMouseLeave(ev : Event) : void
{
stage.removeEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove);
stage.removeEventListener(Event.MOUSE_LEAVE, _onMouseLeave);
}
}
}
通过对这些代码的学习和实践,开发者可以更好地掌握3D场景中的交互技术,为自己的项目增添丰富的交互功能。
超级会员免费看
1002

被折叠的 条评论
为什么被折叠?



