write by 九天雁翎(JTianLing) -- www.jtianling.com
讨论新闻组及文件
其实在上一节,游戏已经基本成型了,但是还不能算是完整的游戏,本篇将需要完善的部分完成吧。
游戏加个边框
这个实在不需要我额外讲方法了,按原来的办法加上四周的边框即可。不过这里讲几个个人总结的技巧,边框需不需要显示呢?需要显示的话,那没有话说,直接创造一个边框就可以了,不需要显示呢?我想出了两个办法,其一,将边框创造在显示范围以外,那么自然是看不见了。其二,创建完全透明的图,那么你就可以将边框放置在任何你想要加碰撞阻挡,却不希望玩家看到的位置了。其三,不透明的图其实也行,只需要放置在Z轴超出视景体即可(大于远剪裁面,小于等于近剪裁面),由于box2D是2D的物理引擎,会忽略Z轴,同样的,只要X,Y轴对了,你还是能够获取到你想要的碰撞效果。
这里由于懒得做资源了,直接使用Orx作者的资源和配置,所以使用第一种方法。
首先,用作者的wall.png创造四周的墙壁,为了先看到直观的看到碰撞效果,同时也为了大概确认位置正确,我们首先将墙壁置为可见。
配置如下:
; Wall
[WallTemplate]
Graphic =
WallGraphic
Body =
WallBody
BlendMode =
alpha
[WallGraphic]
Texture =
data/wall.png
Pivot =
center
[Walls]
ChildList =
Wall1 # Wall2 # Wall3 # Wall4
[Wall1@WallTemplate]
Position =
(0.0, 250, 0.0)
Rotation =
90.0
[Wall2@WallTemplate]
Position =
(-170, 0.0, 0.0)
[Wall3@WallTemplate]
Position =
(0.0, -250, 0.0)
Rotation =
90.0
[Wall4@WallTemplate]
Position =
(170, 0.0, 0.0)
[WallBody]
PartList =
WallBox
Dynamic =
false
[WallBox]
Type =
box
Friction =
1.0
Restitution =
0.0
SelfFlags =
0x0001
CheckMask =
0x0001
Solid =
true
我新建了一个名为wall.ini的配置文件,此时在原来的game.ini中添加下面这条语句表示包含。
@wall.ini@
其他的配置的含义在上一节中已经提到过,这里不重复了。
然后,在初始化的时候新添加一条代码创造墙壁。
if (orxObject_CreateFromConfig("Walls") == NULL) {
result = orxSTATUS_FAILURE;
}
看到这里,提一下Orx作者iarwain推荐的方法,iarwain推荐大量的不需要直接获取指针的对象可以通过一个类似于scene的配置端来配置,然后通过ChildList字段串起来,由于一个ChildList的列表长度是255个,(已经够多了)假如还不够的话,任意一个ChildList指定的对象还可以是仅包含ChildList的字段。。。。。如此串起来,无穷尽也。。。。以此构建整个场景。。。。。好处是可以动态添加新的东西,完全不用像我一样添加代码。
然后,就可以看到正常的碰撞效果了。而且也可以看到墙壁存在。要将难看的墙壁消去,只需要修改配置将墙壁移到显示范围外即可(记得计算墙壁本身的宽度)
此例中部分配置实际的为如下内容即可:(将墙都往屏幕外移动10像素)
[Wall1@WallTemplate]
Position =
(0.0, 260, 0.0)
Rotation =
90.0
[Wall2@WallTemplate]
Position =
(-180, 0.0, 0.0)
[Wall3@WallTemplate]
Position =
(0.0, -260, 0.0)
Rotation =
90.0
[Wall4@WallTemplate]
Position =
(180, 0.0, 0.0)
响应输入
有人将游戏称为第9艺术,并且,游戏与电影的区别就在于更多的交互,这里,我们来谈谈交互^^当然,那就是响应玩家的输入,并且做出反应罗。这里是用Win32平台来做例子的,所以,就说键盘输入吧。
首先,为了不在Run函数中去响应键盘输入,我添加一个新的自己的Update函数,需要是用一个计时器,相关的知识可以参考官方的中文WIKI
。
需要用下面两句注册一个Update的回调函数,这里创建的是20HZ的clock。
orxCLOCK *pstClock = orxClock_Create(orx2F(0.05f
), orxCLOCK_TYPE_USER);
orxClock_Register(pstClock, GameApp::Update, NULL
, orxMODULE_ID_MAIN, orxCLOCK_PRIORITY_NORMAL);
然后就可以在Update里面处理我们的的输入了。
首先,配置部分,很简单。
[Input]
SetList =
Input
KEY_LEFT =
GoLeft
KEY_RIGHT =
GoRight
主要就是配置Input配置段,这里的SetList一般情况下可以是其他配置段的名字,并且可以是一个list,这里为求简单,我指向了自身(配置简单化大法之一),这样就可以省下一个section。这里的配置表示按左方向键表示GoLeft的动作,按右方向键表示GoRight的动作。
我们在代码里面看GoLeft和GoRight怎么用的:
void
GameApp::Update(const
orxCLOCK_INFO *_clock_info, void
*_context)
{
#define MOVE_SPEED
10
if
( orxInput_IsActive("GoLeft"
) ) {
orxVECTOR pos;
orxObject_GetPosition(gPaddle, &pos);
pos.fX -= MOVE_SPEED;
orxObject_SetPosition(gPaddle, &pos);
} if
(orxInput_IsActive("GoRight"
)) {
orxVECTOR pos;
orxObject_GetPosition(gPaddle, &pos);
pos.fX += MOVE_SPEED;
orxObject_SetPosition(gPaddle, &pos);
}
}
这里的gPaddle就是全局的paddle指针。然后,此时的游戏已经可以玩了。按左右方向键移动paddle即可。
这里再贴一下新的完整代码,有部分修改,还有一些改进前一节教程的内容
1
2
#include
"orx.h"
3
4
#include
5
#include
6
using
namespace
std;
7
orxOBJECT* gPaddle;
8
class
GameApp
9
{
10
public
:
11
static
orxSTATUS orxFASTCALL EventHandler(const
orxEVENT *_pstEvent);
12
static
orxSTATUS orxFASTCALL Init();
13
static
void
orxFASTCALL Exit();
14
static
orxSTATUS orxFASTCALL Run();
15
static
void
orxFASTCALL Update(const
orxCLOCK_INFO *_clock_info, void
*_context);
16
17
GameApp() {};
18
~GameApp() {};
19
20
static
GameApp* Instance() {
21
static
GameApp instance;
22
return
&instance;
23
}
24
25
private
:
26
orxSTATUS InitGame();
27
};
28
29
#define BLOCK_TYPE
1
30
31
orxOBJECT *CreateText(orxSTRING _zTextSection)
32
{
33
orxConfig_PushSection(_zTextSection);
34
orxConfig_SetString("Graphic"
, _zTextSection);
35
orxConfig_SetString("Text"
, _zTextSection);
36
37
orxOBJECT *pstReturn = orxObject_CreateFromConfig(_zTextSection);
38
39
orxConfig_PopSection();
40
41
return
pstReturn;
42
}
43
44
// Init game function
45
orxSTATUS GameApp::InitGame()
46
{
47
orxSTATUS result = orxSTATUS_SUCCESS;
48
orxCLOCK *pstClock = orxClock_Create(orx2F(0.05f
), orxCLOCK_TYPE_USER);
49
orxClock_Register(pstClock, GameApp::Update, NULL
, orxMODULE_ID_MAIN, orxCLOCK_PRIORITY_NORMAL);
50
51
52
// Creates viewport
53
if
( orxViewport_CreateFromConfig("Viewport"
) == NULL
) {
54
result = orxSTATUS_FAILURE;
55
}
56
57
if
(orxObject_CreateFromConfig("Ball"
) == NULL
) {
58
result = orxSTATUS_FAILURE;
59
}
60
61
62
if
( (gPaddle = orxObject_CreateFromConfig("Paddle"
)) == NULL
) {
63
result = orxSTATUS_FAILURE;
64
}
65
66
if
(orxObject_CreateFromConfig("Blocks"
) == NULL
) {
67
result = orxSTATUS_FAILURE;
68
}
69
70
if
(orxObject_CreateFromConfig("Walls"
) == NULL
) {
71
result = orxSTATUS_FAILURE;
72
}
73
74
orxEvent_AddHandler(orxEVENT_TYPE_PHYSICS, GameApp::EventHandler);
75
// Done!
76
return
result;
77
}
78
79
// Event handler
80
orxSTATUS orxFASTCALL GameApp::EventHandler(const
orxEVENT *_pstEvent)
81
{
82
orxSTATUS eResult = orxSTATUS_SUCCESS;
83
if
(_pstEvent->eType == orxEVENT_TYPE_PHYSICS) {
84
if
( _pstEvent->eID == orxPHYSICS_EVENT_CONTACT_REMOVE ) {
85
/*
Gets colliding objects
*/
86
orxOBJECT *object_recipient = orxOBJECT(_pstEvent->hRecipient);
87
orxOBJECT *object_sender = orxOBJECT(_pstEvent->hSender);
88
89
string recipient_name(orxObject_GetName(object_recipient));
90
string sender_name(orxObject_GetName(object_sender));
91
if
(sender_name.substr(0
, sender_name.length()-1
) == "Block"
) {
92
// 这样比直接删除要安全
93
orxObject_SetLifeTime(object_sender, orxFLOAT_0);
94
}
95
}
96
}
97
// Done!
98
return
orxSTATUS_SUCCESS;
99
}
100
101
void
GameApp::Update(const
orxCLOCK_INFO *_clock_info, void
*_context)
102
{
103
#define MOVE_SPEED
10
104
if
( orxInput_IsActive("GoLeft"
) ) {
105
orxVECTOR pos;
106
orxObject_GetPosition(gPaddle, &pos);
107
pos.fX -= MOVE_SPEED;
108
orxObject_SetPosition(gPaddle, &pos);
109
110
} if
(orxInput_IsActive("GoRight"
)) {
111
orxVECTOR pos;
112
orxObject_GetPosition(gPaddle, &pos);
113
pos.fX += MOVE_SPEED;
114
orxObject_SetPosition(gPaddle, &pos);
115
}
116
}
117
// Init function
118
orxSTATUS GameApp::Init()
119
{
120
orxSTATUS eResult;
121
orxINPUT_TYPE eType;
122
orxENUM eID;
123
124
/*
Gets input binding names
*/
125
orxInput_GetBinding("Quit"
, 0
, &eType, &eID);
126
const
orxSTRING zInputQuit = orxInput_GetBindingName(eType, eID);
127
128
// Logs
129
orxLOG("
n
- '
%s
' will exit from this tutorial"
130
"
n
* The legend under the logo is always displayed in the current language"
, zInputQuit );
131
132
orxLOG("Init() called!"
);
133
134
// Inits our stand alone game
135
eResult = GameApp::Instance()->InitGame();
136
137
// Done!
138
return
eResult;
139
}
140
141
// Exit function
142
void
GameApp::Exit()
143
{
144
145
// Logs
146
orxLOG("Exit() called!"
);
147
}
148
149
// Run function
150
orxSTATUS GameApp::Run()
151
{
152
orxSTATUS eResult = orxSTATUS_SUCCESS;
153
154
155
// Done!
156
return
eResult;
157
}
158
159
160
// Main program function
161
int
main(int
argc, char
**argv)
162
{
163
// Inits and runs orx using our self-defined functions
164
orx_Execute(argc, argv, GameApp::Init, GameApp::Run, GameApp::Exit);
165
166
// Done!
167
return
EXIT_SUCCESS
;
168
}
169
170
171
#ifdef __orxMSVC__
172
173
// Here's an example for a console-less program under windows with visual studio
174
int
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int
nCmdShow)
175
{
176
// Inits and executes orx
177
orx_WinExecute(GameApp::Init, GameApp::Run, GameApp::Exit);
178
179
// Done!
180
return
EXIT_SUCCESS
;
181
}
182
183
#endif
// __orxMSVC__
一共183行。我并没有特意的精简代码,比如有两个可供切换的Main函数部分,有很多可以Init部分其实也都可以通过iarwain的方式缩减。
全部的源代码在:https://orx-sample.jtianling.googlecode.com/hg/
上,可以通过mercurial直接获取。
这里同样提供一个对比的参考。
《How To Create A Breakout Game with Box2D and Cocos2D Tutorial: Part 1/2
》
《How To Create A Breakout Game with Box2D and Cocos2D Tutorial: Part 2/2
》
点击链接下载源代码:Cocos2D and Box2D Breakout Game
原创文章作者保留版权 转载请注明原作者 并给出链接
write by 九天雁翎(JTianLing) -- www.jtianling.com
阅读全文....
本文译自 orx tutorials 的
physics tutorial
,六月的流光 译。最新版本见Orx 官方中文Wiki
。 本文转自六月的流光的博客
。原文链接在:http://yatusiter.blogbus.com/logs/68692886.html
。
Physics
tutorial
物理特性教程
Summary
综述
See previous
basic
tutorials
for more info about basic
object
creation
,
clock
handling
,
frames
hierarchy
,
animations
,
cameras
& viewports
,
sounds
& musics
and
FXs
.
查看之前关于
basic
object
creation
,
clock
handling
,
frames
hierarchy
,
animations
,
cameras
& viewports
,
sounds
& musics
and
FXs
.以获得更多的
信息。
This
tutorial shows how to add physical properties to objects and handle
collisions.
本
教程展示了如何为对象添加物理属性和处理碰撞。
As you can see, the physical properties are
completely data-driven. Thus, creating an object with physical
properties (ie. with a body) or without results in the exact same line
of code.
如你所见,物理
属性完全是数据驱动的。因此,可以在完全相同的一行代码创建一个拥有物理属性(即通过一个body)或没有物理属性的对象。
Objects
can be linked to a body which can be static or dynamic.
对象可以动态或
静态地链接到一个body(译注:body为Box2D中的一个概念,表示一个物理实体的概念)。
Each
body can be made of up to 8 parts.
每一个body可以由8部分组成。
A body
part is defined by:
一个body部分可以定义如下:
●
its shape (currently box,
sphere and mesh (ie. convex polygon) are the only available)
●
information about the shape size (corners for
the box, center and radius for the sphere, vertices for the mesh)
●
if no size data is specified, the shape will try
to fill the complete body (using the object size and scale)
●
collision “self” flags that defines this part
●
collision “check” mask that defines with which
other parts this one will collide
1)
(
two
parts in the same body will never collide
)
●
a flag (Solid) specifying if this shaped should
only give information about collisions or if it should impact on the
body physics simulation (bouncing, etc…)
●
various attributes such as
restitution, friction, density, …
●
它的形状(目前可用的只有箱子(
box,译注:长方体)、球体(sphere)和多边形(即 mesh,凸多边形))
●
关于形状大小的信息(箱子的拐角点,球体的球心和半径以及
凸多边形的顶点)
●
如果有没指定形状的大小数据,形状会尝试填满整个body(根据对象的大小和缩放)
●
“self”标记定义产生碰撞的部分
●
“check”掩码定义了与这部分产生碰撞的其他部分
(注释1 同一个物理的两个部分永远不会碰撞)
●
一个标记(Solid)指定了这个形状是否只给出碰撞的
信息还是应该影响body的模拟物理运动(弹跳等)
●
其他的各种属性如
弹性
(restitution)
、摩擦
(friction)、密度(density)……
In this tutorial we create static solid
walls around our screen. We then spawn boxes in the middle.
The
number of boxes created is tweakable through the config file and is 100
by default.
在
本教程中我们创建环绕我们屏幕的静态固体墙。然后我们在中间放置箱子。
要创建的箱子数目是可以通过配置文件调整的,默认为100。
The
only interaction possible is using left and right mouse buttons (or left
and right keys) to rotate the camera.
As we rotate it, we also update the
gravity vector of our simulation.
Doing so, it gives the impression that
the boxes will be always falling toward the bottom of our screen no
matter how the camera is rotated.
唯一可能的交互操作是使用鼠标的左右键(或者键盘左右方向键)来旋转摄像头。
在旋转时也将会
更新我们的模拟的重力矢量。
如此,会让我们有无论摄像头怎么旋转那些箱子都一直往我们屏幕底部掉落的感觉。
We also
register to the physics events to add a visual FXs on two colliding
objects.
By
default the FX is a fast color flash and is, as usual, tweakable in
realtime (ie. reloading the config history will apply the new settings
immediately as the FX isn't kept in cache by default).
我们也可以注册
物理事件来为两个碰撞的物体添加可视的特效(FXs)。
默认特效(FX)是一种快速的颜色闪烁,通常也是可以实时调整的(即,重新读取配置文
件会使新设置立即生效,因为特效默认是不保存在缓冲中的)。
Updating an object scale (including
changing its scale with FXs) will update its physical properties (ie.
its body).
Keep in mind that scaling an object with a
physical body is more expensive as we have to delete the current shapes
and recreate them at the correct size.
This is done this way as our current
single physics plugin is based on Box2D which doesn't allow realtime
rescaling of shapes.
更新一个对象的缩放比(包括通过FX改变它的缩放比)会更新它的物理属性(即 它的body)。
请注意改变一个
有物理特性的body对象代价是很高的,因为我们必须删除当前的形状并按正确的大小重建。
这么做是因为我
们目前唯一的物理引擎插件是基于Box2D的,它不支持实时重新缩放形状。
This tutorial does only show basic
physics and collision control, but, for example, you can also be
notified with events for object separating or keeping contact.
本教程只展示了
简单的物理特性和碰撞控制,但是,比如说,你也可以获取到对象碰撞和结束碰撞的事件。
Details
详细说明
As usual, we begin by loading our config
file, creating a clock and registering our Update function to it.
Please
refer to the
previous
tutorials
for more details.
就像往常一样,
我们会以加载配置文件,创建一个时钟并注册我们的更新函数作为开始。
请参考前面的教程(LINK)以获得更多信息。
We also
creates our walls. Actually we won't create them one by one, we'll
group them in a ChildList of a parent object.
我们也创建了我
们的墙。实时上我们并不是一个一个的创建,我们把它们在父对象中的一个ChildList组合起来。
orxObject_CreateFromConfig("Walls");
This
looks like we only create one object called Walls, but as we'll see in
the config file, it's actually a container that will spawn a couple of
walls.
这看起来我们只
是创建了一个叫做Walls的对象,但我们会在配置文件中看到,它实际上是一个会产生一堆墙的容器。
Lastly,
we create our boxes.
接着,我们创建我们的箱子。
for(i = 0; i <
orxConfig_GetU32("BoxNumber"); i++)
{
orxObject_CreateFromConfig("Box");
}
As you
can see, we don't specify anything regarding the physics properties of
our walls or boxes, this is entirely done in the config file and is
fully data-driven.
如你所见,我们并没有指定任何关于墙或箱子的属性,这些都在配置文件中完成了并且完全是数据驱动的。
We then
register to physics events.
然后我们注册到物理事件。
orxEvent_AddHandler(orxEVENT_TYPE_PHYSICS,
EventHandler);
Nothing
really new here, so let's have a look directly to our EventHandler
callback.
这
里没有什么新东西,所以我们直接看下EventHandler回调函数。
if(_pstEvent->eID ==
orxPHYSICS_EVENT_CONTACT_ADD)
{
orxOBJECT *pstObject1, *pstObject2;
pstObject1 = orxOBJECT(_pstEvent->hRecipient);
pstObject2 = orxOBJECT(_pstEvent->hSender);
orxObject_AddFX(pstObject1, "Bump");
orxObject_AddFX(pstObject2, "Bump");
}
Basically
we only handle the new contact event and we add a FX called Bump on
both colliding objects. This FX will make them flash in a random color.
基本上我们只处
理了新的联系事件并且我们增加了一个叫做Bump的FX在两个碰撞的对象上。这个FX会使得它们闪烁一种随机的颜色。
Let's
now see our Update function.
现在看看我们的Update函数。
void
orxFASTCALL Update(const orxCLOCK_INFO *_pstClockInfo, void
*_pstContext)
{
orxFLOAT fDeltaRotation = orxFLOAT_0;
if(orxInput_IsActive("RotateLeft"))
{
fDeltaRotation = orx2F(4.0f) *
_pstClockInfo->fDT;
}
if(orxInput_IsActive("RotateRight"))
{
fDeltaRotation = orx2F(-4.0f) * _pstClockInfo->fDT;
}
if(fDeltaRotation != orxFLOAT_0)
{
orxVECTOR vGravity;
orxCamera_SetRotation(pstCamera, orxCamera_GetRotation(pstCamera) +
fDeltaRotation);
if(orxPhysics_GetGravity(&vGravity))
{
orxVector_2DRotate(&vGravity, &vGravity, fDeltaRotation);
orxPhysics_SetGravity(&vGravity);
}
}
}
As you can see, we get the rotation
update from the RotateLeft and RotateRight inputs.
If a
rotation needs to be applied, we then update our camera with
orxCamera_SetRotation() and we update our physics simulation gravity
accordingly.
This way, our boxes will always look like they
fall toward the bottom of our screen, whichever the camera rotation is.
Note
the use of orxVector_2DRotate() so as to rotate the gravity vector.
如你所见,我们
从RotateLeft和RotateRight输入得到了旋转的更新。
如果需要旋转,那么我们就用orxCamera_SetRotation()
来更新摄像头并且我们更新相应的物理特性模拟的重力(矢量)。
这么做,无论摄像头怎么旋转,我们的箱子总会来看起来是向我们屏幕的底部运动。
注意使用
orxVector_2DRotate()以便旋转重力矢量。
NB: All rotations in orx's code are
always expressed in radians!
注意:orx代码中的所有的旋转总是用弧度表示!
Let's
now have a look at our config data. You can find more info on the config
parameters in the
body
section of config settings
.
First, we created implicitely many walls
using the ChildList property. See below how it is done.
现在我们看一下
我们的配置数据。你可以在
body
section of config settings
(LINK 配置设置中的body 配置段)找到更多关于配置参数的信息。
首先,我们通过
ChildList属性隐式地((译者注:原文中的implicitely应为implicitly,应该是作者笔误))创建了许多墙。实现如下:
[Walls]
ChildList
= Wall1 # Wall2 # Wall3 # Wall4; # Wall5 # Wall6
As we
can see, our Walls object is empty, it will just create Wall1, Wall2,
Wall3 and Wall4 (note the ';' ending the list there).You can remove this
';' to create 2 additional walls.
正如我们看到的,我们的墙对象为空,只是创建了Wall1、Wall2、Wall3
和Wall4 (注意 ‘;’ 在列表的结尾)。你可以移除这个 ‘;’ 新增两堵墙。
Let's now see how we define our walls
and their physical properties.
Let's begin with the shape we'll use for
both WallBody and BoxBody.
现在让我们看看怎么定义我们的墙和它们的物理属性。
让我们从我们将
要在WallBody和BoxBody中都用到的形状开始。
[FullBoxPart]
Type
= box
Restitution
= 0.0
Friction
= 1.0
SelfFlags
= 0x0001
CheckMask = 0xFFFF
Solid
= true
Density
= 1.0
Here we
request a part that will use a box shape with no Restitution (ie. no
bounciness) and some Friction.
We also define the SelfFlags and
CheckMask for this wall.
The first ones defines the identity
flags for this part, and the second ones define to which
idendity(应
该是 identity)
flags it'll be sensitive (ie. with who it'll
collide).
Basically, if we have two objects: Object1 and
Object2. They'll collide if the below expression is TRUE.
这里我们创造了
一个没有Restitution(弹性)和一点Friction(摩擦)的box (长方体)形状。
同样我们也为这
堵墙定义了 SelfFlags 和 CheckMask。
第一个(译注:SelfFlags)定义了标识标记,第二个定义了那些它会碰撞到的标
识标记
基
本上,如果我们有两个对象:Object1和Object2.如果以下条件为TRUE,它们就会发生碰撞。
(Object1.SeflFlags
& Object2.CheckMask) && (Object1.CheckMask &
Object2.SelfFlags)
NB: As we don't specify the TopLeft and
BottomRight attributes for this FullBoxPart part, it will use the full
size of the body/object that will reference it.
注意:我们没有
为这个FullBoxPart指定TopLeft 和 BottomRight 属性,所以它会使用所引用的body/object的完整尺寸。
Now we
need to define our bodies for the boxes and the walls.
现在我们需要为
这些箱子和墙定义我们的body。
[WallBody]
PartList
= FullBoxPart
[BoxBody]
PartList
= FullBoxPart
Dynamic = true
We can see they both use the same part
2)
. (they
can have up to 8 parts, but only 1 is used here)
As
Dynamic is set to true for the BoxBody, this object will move according
to the physics simulation.
For the WallBody, nothing is specified
for the Dynamic attribute, it'll then default to false, and walls won't
move no matter what happen to them.
NB: As there can't be any collision
between two non-dynamic (ie. static) objects, walls won't collide even
if they touch or overlap.
我们可以看到他们都使用相同的部分(注释2
它们最多可以使用8个部分,但这里只使用了一个)。
由于BoxBody(箱子的body)的Dynamic属性被设置为TRUE,这个对
象会根据物理特性的模拟移动。
对WallBody(墙的body),没有特别指定Dynamic属性(的值),则会默认设置为
FALSE,并且无论发生什么这些墙都不会移动。
注意:由于两个non-dynamic(即
static,译注设置Dynamic属性为FALSE或不设置)的对象之间不能发生碰撞,即便两堵墙接触或重叠它们也不会碰撞。
Now
that we have our bodies, let's see how we apply them to our objects.
First,
our boxes.
现在我们有了我们的body,让我们看看怎么把它们应用到我们的对象。
首先,我们的箱
子。
[Box]
Graphic
= BoxGraphic
Position = (50.0, 50.0, 0.0) ~ (750.0, 550.0,
0.0)
Body = BoxBody
Scale
= 2.0
As you
can see, our Box has a Body attribute set to BoxBody.
We can
also notice it's random position, which means everytime we create a new
box, it'll have a new random position in this range.
如你所见,我们
的箱子有一个被设置为BoxBody的Body属性。
我们也注意到它的位置随机,这意味着每一次我们创建一个新的箱子,它会有一个在这个范
围内的随机位置。
Let's
now see our walls.
现在看看我们的墙。
[WallTemplate]
Body =
WallBody
[VerticalWall@WallTemplate]
Graphic
= VerticalWallGraphic;
Scale = @VerticalWallGraphic.Repeat;
[HorizontalWall@WallTemplate]
Graphic
= HorizontalWallGraphic;
Scale = @HorizontalWallGraphic.Repeat;
[Wall1@VerticalWall]
Position
= (0, 24, 0)
[Wall2@VerticalWall]
Position
= (768, 24, 0)
[Wall3@HorizontalWall]
Position
= (0, -8, 0)
[Wall4@HorizontalWall]
Position
= (0, 568, 0)
[Wall5@VerticalWall]
Position
= (384, 24, 0)
[Wall6@HorizontalWall]
Position
= (0, 284, 0)
As
we can see we use inheritance once again.
First
we define a WallTemplate that contains our WallBody as a Body attribute.
We then
inherits from this section with HorizontalWall and VerticalWall. They
basically have the same physical property but a different Graphic
attribute.
Now that we have our wall templates for both
vertical and horizontal wall, we only need to specify them a bit more by
adding a position.
That's what we do with Wall1, Wall2, etc…
正如我们看到的
我们再一次使用到了继承。
首先我们定义一个包含设置为WallBody的Body属性的WallTemplate。
然后
HorizontalWall和VerticalWall从这个配置段继承。基本上它们除了一个不同的Graphic属性外拥有相同的物理属性。
现在我们有水平
和垂直的墙模板了,我们只需要再给它们添加一个位置。
这就是我们对Wall1、Wall2,等……所做的
Resources
资源
源代码:
08_Physics.c
配置文件
:
08_Physics.ini
术语:
restitution:
弹性
gravity vector :重力矢量
阅读全文....
write by 九天雁翎(JTianLing) -- www.jtianling.com
讨论新闻组及文件
在Orx1.2版本中新增了对Unicode和自定义字体的支持,至此,Orx可以支持中文的显示了。在"Uni-code to rule them all?
"一文中作者有所提及。这可是主要就是为了中国用户才添加的功能,我自然需要大力支持罗。
首先,由于1.2版本还未发布,(本来代码已经完成了,但是据作者描述,其显卡正好坏了,买新的显卡还没有到,需要新显卡做Linux和Windows版本)所以我使用的SVN上
的版本。
另外,对于此功能,作者已经添加了新的教程内容
,并且此教程已经有中文版本
了,欢迎大家查看学习。同时也在此感谢参与Orx WIKI翻译工作的全体兄弟。
因为此教程,还是讲一些拉丁字符的显示,这里我依据教程内容,真正的完成中文的显示教程。不过最最郁闷的是,目前没有找到很好的支持中文的字体生成工具。也就是将汉字从TTF等格式转成点阵图的工具,这样的英文字体工具很多,但是没有找到合适的支持中文的工具。
另外,处于效率考虑,一般的游戏引擎都是按照图片方式显示文字,这样可以与普通的游戏图片内容一起刷新,速度最快,Orx也一样。所以其实不直接支持TTF的文件的字体,而是支持图片格式的文字。
首先,我们看作者的教程10,用的图片:(因为作者用的是白色图片,为了能够显示出来,这里我进行了反色)
使用方式在原来文字显示的基础上学习其实很简单:(原教程内容)
在 text字段添加Font,表示需要使用自定义的字体,Font的内容为自定义字体的配置段,并且与locale相关,最后是选择的语言配置段中的字体。
[Legend1Text]
String = $Content
Font = $LocalizedFont
自定义的配置段内容如下:
[CustomFont]
Texture = ../../data/object/penguinattack.png
CharacterList = " !""#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz{|}~�€�‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
CharacterSize = (19, 24, 0)
在Texture添加字体的图片,CharacterList添加字体图片对应的字符,CharacterSize是个vector,表示图片中每个字符的大小。就是这些新内容。
现在来看看汉字的使用方式,如法炮制,这里其实还有个问题,因为汉字比较特殊,没有办法像作者添加的ISO字符一样,都放在一个字体文件中,但是现在的Font可以根据locale来修改。
因为没有找到合适的工具,(这是个问题,有人找到好工具了记得告诉我),所以我自己用photoshop拼出了一个汉字的图片(借助一个在线的字体生成网 站)。。。。痛苦啊。
如下:
然后就是改配置罗,
修改教程10的部分配置如下:
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
1
2
[Locale]
3
LanguageList =
English # French # Spanish # German # Finnish # Swedish # Norwegian # Chinese
4
5
[Chinese]
6
Content =
这是囧ㄏㄨ的标志
7
Lang =
(Chinese)
8
LocalizedFont =
ChineseCustomFont
9
10
[ChineseCustomFont]
11
Texture =
../../data/object/CustomChineseFont.png
12
CharacterList =
"这是囧ㄏㄨ的标志"
13
CharacterSize =
(72, 72, 0)
一如既往的,我也帮iarwain强调一句,不用改一句代码,直接运行原来的教程时的程序即可看到显示效果:(注意按空格切换,切换过N多语言以后,就会看到中文了)
对于此例子来讲,几乎看不到使用字体的任何好处,因为每个汉字只显示了一次,但是换换字符内容就能知道好处了。
比如:
[Chinese]
Content =
囧ㄏㄨ的标志这是
Lang =
(Chinese)
LocalizedFont =
ChineseCustomFont
而且,因为没有合适的工具,我在Photoshop中做出来的图其实还是有问题的。。。。。。。后面的空太长,字切的不准。。。。同时,这也反映了教程中作者提出来的问题,所有的自定义字体必须是等宽字体(不然怎么切啊?)
原创文章作者保留版权 转载请注明原作者 并给出链接
write by 九天雁翎(JTianLing) -- www.jtianling.com
阅读全文....
本文来自于 killman.liu 的博客 坪溪笔谈
,原文链接在:http://blog.feihoo.com/2010/04/work-effcient-thoughts.html
因为个人看了后很有感触,在这里向大家推荐。
提高工作效率的方法
最近一段时间以来,效率相当地高。 纵观前些年的工作效率中,少有这么利落快速而且不加班的。 回顾这几年,我工作效率的提高主要有下面的几个方面。
一、集中目标
专注目标。
凡是工作效率高的时间段里,工作目标都很明确。每天一上班,心里就想明白了今天要干什么,每天下班时,想想今天都实现了什么,有什么目标还没有实现。尽管每天早上来了先看看股票新闻,中午饭后看看股票新闻,偶尔下午还打个盹什么的,但工作的时候确实格外专注,丝毫也不会走神。
分离与当前无关的任务/问题。
专注目标不是那么容易做到的。印象中以前工作中经常会碰到的问题是工作中遇到的问题。一方面可能得益于基本功的增强,最近虽然也遇到了一些问题,但是都能够通过简单阅读或查找文档,或浏览问题相关的库的源码解决;
另一方面,遇到的问题我总是避繁就简,首先倾向于寻找简单可用可靠的方案,并将心中的疑虑记录下来,集中成一个列表,工作之外翻翻书,系统思考和学习,而不会因为这个问题而叉开思路对相关的内容研究一番。总之,专注当前的任务,把新问题记录下来,回头再专心攻克。
例如我第一次用ibatis,遇到了一些表可以用集合和映射来加强Pojo的OO功能,但是我并不熟悉ibatis的这些特性,并且使用简单的方案也是可行的,于是就直接使用简便方案(在我看来,ibatis主要是将SQL集中起来管理、简化SQL操纵,对OO不感冒)。
工作列表。
不论是开发还是设计,一个文本格式的位于源代码存储系统下的待办工作列表与IDE中的TODO项一起构成了专注目标的重要工具。工作目标分解是基本工作步骤,记录工作中产生的新的问题(任务),这样,子问题越来越多,项目中要干的事情越来越丰富。经常性地调整当前工作任务列表,根据重要性对这些任务进行划分。每天都干掉一些问题,经常想着那些最重要的问题。
简单但有迹可寻的设计材料。
在思考和工作的过程中,一方面,项目的过程通常比较长,另一方面经验相对丰富的人手头总是有多个事情在并行进行。当时间太长记不清出了或者是切换任务时,经常要查看此前的代码和设计,易于修改以及有历史记录的设计材料对于保持设计思路的完整十分有意义。(设计材料还应该包括重要的图)
正式准确可以依靠的需求文档。
这一条无论怎么说都不过分,遇到需求不确定的地方,就去查需求文档。如果没有,就请能够回答的人来回答,委托给他,先去做别的。细想在上一家公司,经常要承担多种角色,甚至连需求也经常没人给一个最终决策,设计开发的时候在模棱两可的需求之间做决定,很烦心。对于一个设计/开发人员而言,需求人员在需求问题方面就是绝对的权威。如果有些建议,可以建议,但一切均以需求/产品人员为准。
单元测试。
另一有助于集中精力编码的就是单元测试。单元测试让我集中精力实现当前的功能,需要依赖的其他功能,我总是先生成一个接口,让当前的功能通过测试后再专心去实现该接口。一天下来,一个一个的测试点亮了绿色,一个一个的接口被实现,整天都是十分惬意的。单元测试的另外一个收益就是放心地去重构吧,放心地去做新的特性吧。
二、简化问题的能力
无论是在广义的工作方法/工作态度上,还是在针对具体问题的设计/实现上,我认为最重要的个人能力就是化繁为简了。化繁为简是所有工作方法/软件设计的核心。将那些可以砍掉的工作砍掉(最多做个记录),做到尽可能地经济,尽可能地简单。
从工作方法和态度上来讲,真正需要去做的工作才值得去做,大力砍掉那些不应该在当前工作中处理的事情。例如不必要的优化,不必要的扩展性,不必要的性能,不必要的功能,可以不要的技术,不必要的流程,不必要的文档,统统砍掉,一切可以没有的全都不能有
。
工作中也可能遇到非关键的难题,通常绕过它们,使用更简单的方案就是了。纠缠于这些不重要的难题,最容易浪费时间。例如,eclipse忽然坏掉了,最好的方法是重装一个;
又如以前我经常自己建一个持续集成服务器,现在我直接养成每次写完代码手工跑一遍的习惯(前提是够用)。简化你的工作!简化!
从设计/实现来讲,最好的方案就是最简单直接、一眼就能看懂的方案。记得刚到一个新的环境,有一个统计任务,既要用到数据库也要用缓存,我做了精细的设计来保证最终一致性,状态流程都很完整,并且使用线程池来并发运行分批处理,最后再合并。虽然控制得很完美很精细,但是流程和结构都很复杂。上头根据此前的项目经验给出了一个更简单的例子,直接将各个任务划分为多个线程,分开存储,到了该统计的时候,冻结数据在所有的分区上做统计。状态砍掉一半,流程缩减一半。
事实上,作为简单直接的一个附带效应,最简单直接的方式,通常性能也最好。
简化问题的能力,是一个人的核心能力。
三、基本功
基本功的内容十分复杂。首先,对整个计算机体系的理解,对操作系统/虚拟机/数据库本质的理解,对语言基础类和库的理解,我觉得是核心基本功
。
第二项基本功,就是学习能力
。
通过快速阅读核心文档理解核心思想,然后其他的东西总是能从文档中查到就行。细枝末节的东西,即学即用,学过就忘可也。
第三项基本功,就是文档、资料的搜索和收集
。
要想在工作中如行云流水,另外一个方面就是避开暂时还不熟悉的技术和工具,不熟悉的东西很难用好,更难用顺畅。尤其是那些纠结复杂、华而不实的技术,不要去碰。这属于简化能力的范畴。
四、工具
选择工具的核心标准,就是简单朴素可信赖
。
文本格式的设计,加上易于修改的图。
我喜欢用一个简单的文本格式来记录设计,随时修改,随时查阅。而附上几幅简单直接的图,经常能够更简单直接地表达更多的内容。
简单的可信赖的工具。我曾经将很长的时间用来构建Maven的环境上,用Maven管理依赖,尤其是跟Eclipse协作时,经常出现诡异。现在我用ant,或者只用Maven,maven不与eclipse纠缠在一起。如果一个工具出几次诡异现象,那就干脆丢掉它。
版本管理工具。 不仅仅源码要由版本管理,整个项目过程的所有知识,全部用版本管理系统管理起来,集中存放。现在我用subversion和git。
阅读全文....
本文译自 orx tutorials 的 声音和音乐(sound&music)
,~麽黛誌~ 译。最新版本见Orx 官方中文Wiki
。 (因为这里格式不好看,推荐去官方WIKI查看)本文转自~麽黛誌~的博客
。原文链接在:http://blog.csdn.net/v_023/archive/2010/07/06/5717261.aspx
。
参看前面的教程基础
, 对象创建
, 时钟
,
框架层次结构
,
动画
, 视口与摄像机
。
本教程则演示如何播放声音(样本)和音乐(流)。和先前其他功能一样,在大部分时候,只需要一行代码,一切都是数据驱动。
本教程还演示了如何通过士兵的图像作为视觉反馈,展现实时改变的声音设置。
当你按上/下箭头,声音将做出相应的改变。士兵也会因此发生变化。
通过点击左右键,音乐的音调(音频)会相应地改变。士兵将会向收音机的旋钮一样旋转。
左控制键将会在音乐停顿的时候播放音乐(同时激活士兵),并会在音乐播放的情况下暂停音乐(并停止士兵的活动)最后,回车和空格会在士兵上播放出声音效
果。
用空格触发声音效果跟用回车是一样的,唯一的区别在于空格键控制的音量和音调是随机定义在默认配置文件中的。
这种配置控制的频率随机性允许简单步骤或没有多余的代码稍有不同击中的声音。我们随意改变士兵的颜色来说明这一点。声音效果,只会增加和表现在有效士兵角
色。
如果你想表现一个声音效果不用对象来支持,你可以像本教程中创建音乐方法一样。但是,在对象上表现一个声音将需要空间声音定位(本教程不做介绍)。
许多声音效果可以同时表现在一个单一的对象上。
声音的配置属性KeepDataInCache允许保留在内存中,而不是每次都从文件中读取声音样本。这只针对非流数据(即不是音乐类型)。
如果它被设置为false,样本将从文件中重新加载,除非有另一个相同类型的声音效果正在播放。
我们也注册声音事件去得到什么时候开始或停止播放声音。这些事件仅仅在实际播放时才触发。
通常,我们先载入config
file(配置文件),创建一个viewport,创建一个clock(时钟)并且注册Update(更新)函数,最后创建一个主对象。请从之前的教程中
获得更多的信息。
接下来我们来创建一个音乐对象并且播放它。
orxSOUND *
pstMusic;
pstMusic =
orxSound_CreateFromConfig(
"Music"
)
;
orxSound_Play(
pstMusic)
;
正如我们看到的,音乐和声音都属于orxSOUND类型。主要区别在于音乐是流,而声音是完全加载在内存中。
接下来,让我来看它们在设置配置文件上的差异。
初始化函数最后一步:我们添加音频事件响应。
orxEvent_AddHandler(orxEVENT_TYPE_SOUND, EventHandler);
我们只在音频开始/停止记录日志,相应代码如下:
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
orxSOUND_EVENT_PAYLOAD *pstPayload;
pstPayload = (orxSOUND_EVENT_PAYLOAD *)_pstEvent->pstPayload;
switch
(_pstEvent->eID)
{
case
orxSOUND_EVENT_START:
orxLOG("Sound <
%s
>@<
%s
> has started!"
, pstPayload->zSoundName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));
break
;
case
orxSOUND_EVENT_STOP:
orxLOG("Sound <
%s
>@<
%s
> has stoped!"
, pstPayload->zSoundName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));
break
;
}
return
orxSTATUS_SUCCESS;
正如你所看见的,没有什么是新东西的。
现在我们来看怎样去添加一个音频到士兵角色上。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
if(orxInput_IsActive("RandomSFX") && orxInput_HasNewStatus("RandomSFX"))
{
orxObject_AddSound(pstSoldier, "RandomBip");
orxObject_SetColor(pstSoldier, orxColor_Set(&stColor, orxConfig_GetVector("RandomColor", &v), orxFLOAT_1));
}
if(orxInput_IsActive("DefaultSFX") && orxInput_HasNewStatus("DefaultSFX"))
{
orxObject_AddSound(pstSoldier, "DefaultBip");
orxObject_SetColor(pstSoldier, orxColor_Set(&stColor, &orxVECTOR_WHITE, orxFLOAT_1));
}
我们看到的是,添加一个音频到一个士兵角色上只需要一行代码,并且更为重要是随机和固定音频也是这样做的。后面我们会介绍它们在配置文件上的不同。
当我们添加一个RandomBip音频,通过配置文件中定义的key-RandomColor随机改变士兵颜色.当播放DefaultBip时,我们可以
简单地将颜色改回白色。
注意:一个声音将会在每次有对应输入的时候被播放。
到目前为止,我们只关心一个输入是否处于激活状态,现在,我们需要在输入被激活的一瞬间做一些操作。
为此,我们使用orxInput_HasNewStatus()
函数,它将在输入状态变化的时候返回orxTRUE
。(比
如从未激活到激活状态,从激活到未激活状态)
再结合 orxInput_IsActive()可以确保当我们只播放声音时,获取的输入是从非激活到激活的。
现在,让我们一起演示一下。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
if
(orxInput_IsActive("ToggleMusic"
) && orxInput_HasNewStatus("ToggleMusic"
))
{
if
(orxSound_GetStatus(pstMusic) != orxSOUND_STATUS_PLAY)
{
orxSound_Play(pstMusic);
orxObject_Enable(pstSoldier, orxTRUE);
}
else
{
orxSound_Pause(pstMusic);
orxObject_Enable(pstSoldier, orxFALSE);
}
}
通过这个简单的代码可以看到,当我们ToggleMusic的输入激活时,在非播放状态下将开始音乐播放且激活士兵。播放状态下则停止音乐播放且不激活士
兵。
现在,让我们来改变音高。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
if(orxInput_IsActive("PitchUp"))
{
orxSound_SetPitch(pstMusic, orxSound_GetPitch(pstMusic) + orx2F(0.01f));
orxObject_SetRotation(pstSoldier, orxObject_GetRotation(pstSoldier) + orx2F(4.0f) * _pstClockInfo->fDT);
}
没有特别的,降低音高也是如此就是参数换成了PitchDown.
最后,我们来改变音量。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
if(orxInput_IsActive("VolumeDown"))
{
orxSound_SetVolume(pstMusic, orxSound_GetVolume(pstMusic) - orx2F(0.05f));
orxObject_SetScale(pstSoldier, orxVector_Mulf(&v, orxObject_GetScale(pstSoldier, &v), orx2F(0.98f)));
}
具体做法和改变Pitch一样,没有什么特别的。
注意:我们可以看到,只有将我们的对象的旋转时间一致(参见时钟教程
(clock tutorial)
)。
音乐的音高和声量,包括对象的缩放都将是帧相关的(framerate-dependent),这是一个不好的事情。
为了解决这个问题,我们只需要使用the clock's DT 1)
去确定参数即可。2)
我们已经了解代码部分,现在来看下数据部分。
首先,定义下音乐。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[Music]
Music =
../../data/sound/gbloop.ogg
Loop =
true
很容易!如果我们没有明确地定义Loop=true,音乐就不会循环播放。
现在让我们来看看DefaultBip。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[DefaultBip]
Sound =
../../data/sound/bip.wav
KeepInCache =
true;
Pitch =
1.0
Volume =
1.0
和以前一样,KeepInCache属性将确保这音频将永远不会被自动从内存中卸载。
音高和音量明确地定义为不是实际需要的默认值。
最后,让我们来看看我们的RandomBip。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[RandomBip@DefaultBip]
Pitch =
0.1 ~ 3.0
Volume =
0.5 ~ 3.0
我们可以看到,RandomBip从DefaultBip继承。这意味着,如果我们改变了DefaultBip样本,它也可能改变RandomBip。
我们只需改变节距(即频率)和音量随机值。这意味着,每次播放
RandomBip,它就会有不同的频率和数量,而且,所有的这些都不需要改变代码,只需要改变配置即可!
阅读全文....
本文译自 orx tutorials 的
卷轴效果(Scrolling)教程
,tarzan_sp译。最新版本见Orx
官方中文Wiki
,因为格式的原因,推荐所有文章都去官方的WIKI
查看,那里的格式最好。
望有新人能够加入这个翻译者的队伍,早日将
Orx的WIKI页中文化。有兴趣的请加入qq群73063577,并与我取得联系,防止重复翻译。
卷轴效
果(
scrolling
)教程
综述
学
习前面的基础教程来了解更多关于对象创建,时钟控制,框架层次结构,动画,视口和摄像机,音频和音乐,特效和物理特性的基础信息。
此
教程说明了如何去展示一个视差卷轴。
正如你可以看到的,视角切换本身没有用到特别的代码。
事
实上,orx的默认2D 渲染插件将根据你在配置文件中怎么设置对象的属性来帮你设置好视差卷轴。
默认情况下,在
这个教程里,配置''AutoScroll''将被设置为'both',表示两个坐标轴方向上都有滚动。
这意味着当摄像
机移动的时候视差卷轴将在X,Y两个坐标轴上同时发生。
你可以尝试用这个值去设置x,y,甚至移除它。
除了
''AutoScroll''的属性外,你可以找到''DepthScale''属性(深度缩放比)。
这个属性将用来
依据对象离摄像机的距离自动的调整对象的缩放。//
摄像机视锥体越小,自动缩放将会应用的越快。
你可以尝试将对
象定位到摄像机的远&近剪裁平面来获取你感觉合适的视差卷轴和深度缩放比。
你可以通过配置文件来改变视差卷轴的速度(注释:摄像机移动的速度)。就像平时一样,
你可以实时修改它的值并且重新读取配置。
正如你所能看到的,我们的update代码需要简单地移动3D空间里的摄像机就行了。
按下方向键能把摄像机视角按照X轴和Y轴移动,按下control和alt键会让视角按照Z轴移动。
正如前面说的,
所有的视差卷轴效果将会正常发生,因为所有的对象都已近被合适地处理了。((译者注:包括缩放和位置))
在场景里移动摄
像机只需要很少的代码,而且不用去关心任何的滚动效果。((译者注:即这些效果已经自动的实现了))
你对你想要有多
少个滚动平面,以及哪些对象应该被滚动所影响有完全的控制。
最后一点与天空的显示有关。
正如[[frame|框架教程(frame
tutorial)]]里我们所看到的,我们把天空对象的frame设置为摄像机视角的一个子frame。
这意味着在配置
文件里对天空对象位置的设置将总是相对与摄像机位置的相对值。
换句话说,天空将总是跟随着摄像机视角。
当我们把它的深
度值设置为默认值1000时,(注释:与视锥矩形的远剪裁平面的值相同)它将会停留在背景中。
详细说明
就像平时一样,
我们通过加载配置文件,创建一个时钟,注册我们的Update函数。
最
后,我们创建我们的天空背景和我们所有的云对象。
请
回头看前面的教程来了解更多的细节。
现
在,让我们来看Update函数。首先,我们从配置文件得到摄像机速度,然后通过依赖
DT((译者注:指clock结构的DT字段,见下面的例子))来更新它,从而摆脱帧率依赖。
orxVECTOR vScrollSpeed;
orxConfig_SelectSection("Tutorial");
orxConfig_GetVector("ScrollSpeed",
&vScrollSpeed);
orxVector_Mulf(&vScrollSpeed,
&vScrollSpeed, _pstClockInfo->fDT);
到目前为止没有什么是真正新鲜的。//
现在我们需要根据输入改变摄像头移动的vector(向量)。//
if(orxInput_IsActive("CameraRight"))
{
vMove.fX += vScrollSpeed.fX;
}
if(orxInput_IsActive("CameraLeft"))
{
vMove.fX -= vScrollSpeed.fX;
}
if(orxInput_IsActive("CameraDown"))
{
vMove.fY += vScrollSpeed.fY;
}
if(orxInput_IsActive("CameraUp"))
{
vMove.fY -= vScrollSpeed.fY;
}
if(orxInput_IsActive("CameraZoomIn"))
{
vMove.fZ += vScrollSpeed.fZ;
}
if(orxInput_IsActive("CameraZoomOut"))
{
vMove.fZ -= vScrollSpeed.fZ;
最
后我们将这个移动(向量)应 用到摄像头上
orxCamera_SetPosition(pstCamera,
orxVector_Add(&vPosition, orxCamera_GetPosition(pstCamera,
&vPosition), &vMove));
正
如前面陈述的,我们将不需要 写一行代码来控制视差卷轴。
所
有的事将会在配置端被完成。 我们简单地在我们的3D空间里移动我们的摄像头。
让
我们看看配置数据。这是第一 块有我们自己数据的教程部分。
[Tutorial]
CloudNumber = 1000
ScrollSpeed = (300.0, 300.0, 400.0)
正
如你所看见的,我们用ScrollSpeed和CloudNumber两个字段来控制这个程 序。
ScrollSpeed可以在运行时修改然后重载读取配置文件(通过按
Backspace键)进行更新。
现在让我们看看云朵对象
[CloudGraphic]
Texture = ../../data/scenery/cloud.png
Pivot = center
[Cloud]
Graphic
= CloudGraphic
Position = (0.0, 0.0,
100.0) ~ (3000.0, 2000.0, 500.0)
AutoScroll
= both
DepthScale = true
Color = (180, 180, 180) ~ (220, 220, 220)
Alpha = 0.0
Scale = 1.0 ~ 1.5
FXList = FadeIn
激
活视差卷轴的AutoScroll字段和DpthScale地段是非常重要的两个字段。
首
先AutoScroll字段可以使用‘x’的值,‘x’的值或者‘both’的值。
这
将决定针对这个对象的视差卷轴将在哪个坐标轴上发生。
视
差卷轴将根据对象的Z轴坐标(注释:它在摄像机的
视锥矩形里面的深度
)
被渲染。
对象在Z坐标轴上距离摄像机越近,视差卷轴进行得越快。
AutoScrol 的缺省值是none。
DepthScale(深度缩放比)属性将决定渲染插件是根据它的Z轴坐标来调整对象
的比例。
对象在Z轴上距离摄像机越近,它将被显示得越大。
DepathScale(深度缩放比)的缺省值是false
现在让我们看看我们的天空对象
[SkyGraphic]
Texture = ../../data/scenery/sky.png
Pivot = center
[Sky]
Graphic
= SkyGraphic
Scale = (0.5,
0.004167, 1.0)
Position = (0.0, 0.0,
1.0)
ParentCamera = Camera
正
如你所看到的,我们为''Sky''对象设置 了一个ParentCamera(父摄像机),意味着我们的''Sky''将在摄像机的local
space中(本地空间)(注释:它将跟随着摄像机一起移动)。
我
们把天空的位置设为(0.0 , 0.0 , 1.0 ),这意味着它被置于摄像机的视角的中央,同时它也成为摄像机所能看到画面的背景。
当有一个ParentCamera存在的时候,Scale(缩放比)和
Position(坐标) 属性默认将在父对象的空间中表示,除非把 UseParentSpace(使用父级空间)设置为false。
因此把scale(缩放比)的值设置为’weird’(古怪的)。如果我们有一个对象
是由单像素 构成的,一个缩放比设置为( 1.0 , 1.0 , 1.0 )的对象将覆盖整个父级摄像机所能看到的画面。
因为我们的sky.png位图在X轴上的宽度是2像素,我们在X轴方向需要一个大小为
0.5的 scale(缩放比)
同样的方式,因
为它在Y坐标轴上的长度是240 像素,我们在Y轴上需要一个大小为1/240的scale(缩放比)
资源
源代码:
09_Scrolling.c
配置文件:
09_Scrolling.ini
阅读全文....
本文译自 orx tutorials 的
独立程序与本地化教程
(stand alone &
localization),六月流光 译。最新版本见Orx
官方中文Wiki
。 本文转自六月流光的
博客
。原文链接在:
http://yatusiter.blogbus.com/logs/68485225.html
。望有新人能够加入这个翻译者的队伍,早日将
Orx的WIKI页中文化。有兴趣的请加入qq群73063577,并与我取得联系,防止重复翻译。
StandAlone tutorial
独立程序与本地化教程(stand alone
& localization)
Summary
综述
This is our first basic C++ tutorial. It also
shows how to write a stand alone executable using orx and how to use
the localization module (orxLOCALE).
这是我们的第一个C++基础教程。它也展示了如何使用orx编写独立的可执行文件和使用本地化模
块(orxLOCALE)。
As
we are NOT using the default executable anymore for this tutorial, its
code will be directly compiled into the executable and not into an
external library.
由
于我们在此教程中不再使用默认的可执行文件,它的代码会被直接编译成可执行文件而不是外部库。
This implies that we
will NOT have the default hardcoded behavior we had in the previous
tutorials:
这暗示了我们不再有前几个教程中的如下默认行
为:(译注:内置于默认可执行文件,同时下面的几点原本也都是否定句式,但与这里的否定矛盾,原意应该是下面这些特性会在StandAlone版本中失
效)
- F11 will not affect vertical sync toggler
- Escape
won't automatically exit
- F12 won't capture a
screenshot
- Backspace won't reload configuration files
- the
[Main] section in the config file won't be used to load a plugin
(“GameFile” key)
- F11 切换垂直同步
- Escape
退出
- F12 截屏
- 退格键(Backspace)
重新载入全部配置文件
- 配置文件中的[Main] 配置段被用于加载一个插件(“GameFile” 键)
A program based
directly on orx
1)
, by default, will
also NOT exit if it receives the orxSYSTEM_EVENT_CLOSE event.To do
so, we will either have to use the helper orx_Execute() function (
see below
) or handle it
ourselves.
一个直接基于orx的程序(注释1,即不再依靠
ORX 启动器),默认收到orxSYSTEM_EVENT_CLOSE事件后不退出。为了改变这种情况,我们必须使用orx_Execute()
函数(如下link)或者自己处理。
See
previous
basic tutorials
for more info about
basic
object creation
,
clock handling
,
frames hierarchy
,
animations
,
cameras &
viewports
,
sounds &
musics
,
FXs
,
physics
and
scrolling
.
查看之前关于basic
object creation
,
clock handling
,
frames hierarchy
,
animations
,
cameras &
viewports
,
sounds &
musics
,
FXs
,
physics
and
scrolling
的基础教程以获得更多的信息。
As we're on our own
here, we need to write the main function and initialize orx manually.The
good thing is that we can then specify which modules we want to use,
and deactivates display or any other module at will, if needed.
我们现在只能靠自己了,所以必须自己写main
函数和手动初始化orx。
好
处是如果需要,我们可以任意指定要使用哪些模块,停用显示模块或其他模块。
If we still want a semi-automated
initialization of orx, we can use the orx_Execute() function.This
tutorial will cover the use of orx with this helper function, but you
can decide not to use it if its behavior doesn't suit your needs.
如果我们还是想要半自动初始化orx,我们可以
使用orx_Execute()函数。
本教程将通过这个辅助函数使用orx,但是如果它的行为不符合你的需求也可以不使用。
This helper function
will take care of initializing everything correctly and exiting
properly.
It
will also make sure the clock module is constantly ticked (as it's part
of orx's core) and that we exit if the orxSYSTEM_EVENT_CLOSE event is
sent.
This
event is sent when closing the windows, for example, but it can also be
sent under your own criteria (escape key pressed, for example).
这个辅助函数将会确保所有部分正确初始化和妥善
退出。
它
也将确保clock模块正常运作(作为 orx的核心部分)并且当(备注,一个模块不断被触发,不太好理解,事实上是表示clock一直在正常计时)
orxSYSTEM_EVENT_CLOSE事
件发送时退出。
此
事件在关闭窗口时发送,但是也可以按你的标准发送(例如按下Esc键)。
This code is also a basic C++ example to show
how to use orx without having to write C code.
This tutorial could
have been architectured in a better way (cutting it into pieces with
headers files, for example) but we wanted to keep a single file per
*basic* tutorial.
这
些代码同样也是一个基础的C++示例,用来演示如何通过C语言以外的语言来使用orx。
本教程本来应该用另外一种更好的方式组织起来
(例如分割成多个头文件)但是我们希望每个基础教程都只有一个文件。
This stand alone executable also creates a
console (as does the default orx executable), but you can have you own
console-less program if you wish.
In order to achieve that, you only need to
provide an argc/argv style parameter list that contains the executable
name.
If
you don't, the default loaded config file will be orx.ini instead of
being based on our executable name (ie. 10_StandAlone.ini).
这个独立可执行文件会创建一个终端(和默认的
orx可执行文件一样),但是如果你喜欢也可以创建一个没有终端的可执行文件。
为了实现上述目标,你只需要提供一个包含该可执行文件名的argc/argv风格的
参数表即可。
如
果不这样的话,默认加载的配置文件是orx.ini,取代基于可执行文件名的配置文件(例如 10_StandAlone.ini)。
For
visual studio
users (windows), it
can easily be achieved by writing a WinMain() function instead of
main(), and by getting the executable name (or hardcoding it, as it's
shamelessly done in this tutorial
对visual studio
用户(windows)而言,可以轻松通过撰写WinMain() 替代main()
函数,并且获取可执行文件的名称(或者硬编码实现,正如本教程中可耻地这么做鸟^.^) 。
This tutorial simply
display orx's logo and a localized legend. Press space or click left
mouse button to cycle through all the availables languages for the
legend's text.
本
教程简单的现实了orx的logo和一个本地化的说明。按空格键或者点击鼠标左键以循环显示所有可用语言的说明文本。
Some explanations
about core elements that you can find in this tutorial:
下面是一些你可以在本教程中找到的核心元素的解
释:
- Run
function: Don't put *ANY* logic code here, it's only a backbone where
you can handle default core behaviors (tracking exit or changing locale,
for example) or profile some stuff. As it's directly called from the
main loop and not part of the clock system, time consistency can't be
enforced. For all your main game execution, please create (or use an
existing) clock and register your callback to it.
- 运行
函数(Run
function):不要在这里放置任何逻辑代码,它只是一个处理默认核心行为(例如跟踪退出或者更改locale)或简要描述一些东西的主干。由于它直
接从main循环中调用并且不是clock系统的的一部分,时间一致性无法被强制执行。对你所有的主游戏可执行文件,请在此创建(或者使用已有
的)clock并且注册你的回调函数。
- Event handlers: When
an event handler returns orxSTATUS_SUCCESS, no other handler will be
called after it for the same event. On the other hand, if
orxSTATUS_FAILURE is returned, event processing will continue for this
event if other handlers are listening this event type. We'll monitor
locale events to update our legend's text when the selected language is
changed.
- 事件处理器(Event
handlers):当一个事件响应函数返回orxSTATUS_SUCCESS后,对此事件就不会再调用其他事件响应函数。另一方面,如果返回的是
orxSTATUS_FAILURE,事件会传递给其他正在监听这个事件的响应函数。我们将监视locale事情以便当选择的语言切换时更新我们的说明文
本。(备注:这个翻译有点乱,主要要了解Orx的事件处理方式)
- orx_Execute():
Inits and executes orx using our self-defined functions (Init, Run and
Exit). We can of course not use this helper and handles everything
manually if its behavior doesn't suit our needs. You can have a look at
the content of orx_Execute()
2)
to have a better idea
on how to do this.
- orx_Execute():通过我们自定义
的函数(Init,Run和Exit)初始化和执行orx。当然,如果它不符合我们的需求,我们可以不使用这个辅助函数并且手动处理所有事情。你可以通过
参考orx_Execute()的内容(注2,在orx.h中实现)了解怎么这样做。(备注,作者的better
idea不是说更好的注意,而是对于how to do this的修饰,就是我们不知道时候说,I have no idea一样)
Details
详细说明
Let's start with the
includes.
让
我们从包含的文件开始。
#include "orx.h"
That's all you need to
include so as to use orx. This include works equally with a C or a C++
compiler
3)
.
这就是你要使用orx所需要包含的唯一文件。它在C和C++(注释3,在这种情 况下预编译宏 __orxCPP__
会被自动定义)编译器下都工作良好。
Let's
now have a look at our StandAlone class that contains orx's Init(),
Run() and Exit() callbacks.
现在看下我们的StandAlone类,包含orx
Init()、Run()和Exit()回调函数。
class StandAlone
{
public:
static orxSTATUS
orxFASTCALL EventHandler(const orxEVENT *_pstEvent);
static
orxSTATUS orxFASTCALL Init();
static void orxFASTCALL Exit();
static
orxSTATUS orxFASTCALL Run();
void SelectNextLanguage();
StandAlone() :
m_poLogo(NULL), s32LanguageIndex(0) {};
~StandAlone()
{};
private:
orxSTATUS InitGame();
Logo *m_poLogo;
orxS32
s32LanguageIndex;
};
All
the callbacks could actually have been defined out of any class. This
is done here just to show how to do it if you need it.
We see that our
StandAlone class also contains our Logo object and an index to the
current selected language.
所有的回调函数实际上都可以定义在任何类之外。这里这么做只是演示当你需要的时候你可以这么做。
我们看到StandAlone类也包含了我们的logo对象和一个当前选中的语言的索引。
Let's now have a look
to our Logo class definition.
现在看一下Logo类的定义:
class Logo
{
private:
orxOBJECT
*m_pstObject;
orxOBJECT *m_pstLegend;
public:
Logo();
~Logo();
};
Nothing fancy here, we
have a reference to an orxOBJECT that will be our logo and another one
that will be the displayed localized legend.
As you'll see we won't
use the reference at all in this executable, we just keep them so as to
show a proper cleaning when our Logo object is destroyed. If we don't
do it manually, orx will take care of it when quitting anyway.
这里没有什么特别的,我们用指针指向一个
orxOBJECT作为我们的logo,另一个用来指向显示的本地化说明。如你所见,我们在这个可执行文件里将不会使用这个可执行文件的所有引用,我们只
是保持它们以便显示在销毁Logo对象时被正确地清理。如果我们不手动做,在退出的时候orx会替我们搞定。
Let's now see its
constructor.
现在让我们看一下它的构造函数:
Logo::Logo()
{
m_pstObject =
orxObject_CreateFromConfig("Logo");
orxObject_SetUserData(m_pstObject, this);
m_pstLegend =
orxObject_CreateFromConfig("Legend");
}
As seen in the previous tutorials we
create our two objects (Logo and Legend) and we link our Logo C++ object
to its orx equivalent using orxObject_SetUserData().
用前面的教程中讲的方法,我们创建了两个对象
(Logo和Legend)并且我们通过
orxObject_SetUserData()把Logo C++对象链接到它对应的orx对象上。
Logo::~Logo()
{
orxObject_Delete(m_pstObject);
orxObject_Delete(m_pstLegend);
}
Simple cleaning here
as we only delete our both objects.
这里只是简单的删除了我们的两个对象。
Let's now see our main
function.
现
在看看我们的main函数:
int
main(int argc, char **argv)
{
orx_Execute(argc, argv,
StandAlone::Init, StandAlone::Run, StandAlone::Exit);
return
EXIT_SUCCESS;
}
As
we can see, we're using the orx_Execute() helper that will initialize
and execute orx for us.
In order to do so, we need to provide it our
executable name and the command line parameters along with three
callbacks: Init(), Run() and Exit().
正如我们看见的,我们使用 orx_Execute()
辅助函数来初始化和执行orx。
这样我们需要提供我们的可执行文件名称和命令行参数和三个回调函数:Init()、Run() 和Exit()。
We will only exit from
this helper function when orx quits.
只有当orx退出时我们才从这个辅助函数退出。
Let's have a quick
glance at the console-less version for windows.
我们快速浏览一下windows的无终端版本。
#ifdef __orxMSVC__
int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE PrevInstance,
LPSTR
lpCmdLine, int nCmdShow) {
// Inits and executes orx
orx_WinExecute(StandAlone::Init, StandAlone::Run, StandAlone::Exit);
// Done!
return
EXIT_SUCCESS;
} #endif
Same as for the traditional main() version
except that we use the orx_WinExecute() helper that will compute the
correct command line parameters and use it.
4)(the ones given
as parameter don't contain the executable name which is needed to
determine the main config file name
)
This only works for a
console-less windows game
5)
.(which uses WinMain()
instead of main())
和
旧的main()
版本一样除了我们使用orx_WinExecute()辅助函数来计算正确的命令行参数并使用它(注释4,这些给出的参数并没有包含我们需要的用来决定主
配置文件名的可执行文件的名字)。
这只能在一个无命令行(终端)的windows游戏下工作。(注释5,使用WinMain()替代main())
Let's now see how our
Init() code looks like.
现在看看我们的Init() 代码是怎么样的:
orxSTATUS
StandAlone::Init()
{
orxLOG("10_StandAlone Init() called!");
return
soMyStandAloneGame.InitGame();
}
We simply initialize our StandAlone instance
by calling its InitGame() method.
我们简单地用StandAlone实例中的InitGame()方法来初始化。
Let's see its content.
让我们看看它的内容:
orxEvent_AddHandler(orxEVENT_TYPE_LOCALE,
EventHandler);
m_poLogo = new Logo();
std::cout << "The available
languages are:" << std::endl;
for(orxS32 i = 0; i <
orxLocale_GetLanguageCounter(); i++)
{
std::cout << " - "
<< orxLocale_GetLanguage(i) << std::endl;
}
orxViewport_CreateFromConfig("Viewport");
We simply register a
callback to catch all the orxEVENT_TYPE_LOCALE events.
We then instanciate
our Logo object that contains both logo and legend.
We also outputs all
the available languages that have been defined in config files. We could
have used the orxLOG() macro to log as usual (on screen and in file),
but we did it the C++ way here to show some diversity.
We finish by creating
our viewport, as seen in all the previous tutorials.
我们简单地注册了一个捕捉所有
orxEVENT_TYPE_LOCALE事件的回调函数。
然后实例化所有在配置文件中定义的可用语言。我们通常可以用orxLOG()
宏来记录(在屏幕上和文件里),但是我们这里用C++的方式来实现以表示多样性。
我们通过创建一个视口来结束,就像我们在之前所有教程中做的那样。
Let's now see our
Exit() callback.
现
在看下我们的Exit()回调函数:
void
StandAlone::Exit()
{
delete soMyStandAloneGame.m_poLogo;
soMyStandAloneGame.m_poLogo = NULL;
orxLOG("10_StandAlone Exit()
called!");
}
Simple Logo object
deletion here, nothing surprising.
没有什么特别的,简单地在这里销毁了Logo对象。
Now let's have a look
to our Run() callback.
让我们看下Run()回调函数:
orxSTATUS
StandAlone::Run()
{
orxSTATUS eResult = orxSTATUS_SUCCESS;
if(orxInput_IsActive("CycleLanguage") &&
orxInput_HasNewStatus("CycleLanguage"))
{
soMyStandAloneGame.SelectNextLanguage();
}
if(orxInput_IsActive("Quit"))
{
orxLOG("Quit action triggered, exiting!");
eResult =
orxSTATUS_FAILURE;
}
return eResult;
}
Two things are done here.
First when the input
CycleLanguage is activated we switch to the next available language,
then when the Quit one is activated, we simply return orxSTATUS_FAILURE.
When the Run()
callback returns orxSTATUS_FAILURE orx (when used with the helper
orx_Execute()) will quit.
这里完成了两件事情。
首先我们当CycleLanguage被激活时,我们切换到下一个可用的语言,其次
如果Quit被激活,我们简单地返回orxSTATUS_FAILURE。
当Run()回调函数返回orxSTATUS_FAILURE时orx(使用
orx_Execute()辅助函数)将会退出。
Let's have a quick look to the
SelectNextLanguage() method.
让我们快速的浏览一下 SelectNextLanguage() 方法。
void
StandAlone::SelectNextLanguage()
{
s32LanguageIndex = (s32LanguageIndex ==
orxLocale_GetLanguageCounter() - 1) ? 0 : s32LanguageIndex + 1;
orxLocale_SelectLanguage(orxLocale_GetLanguage(s32LanguageIndex));
}
We basically go to the
next available language (cycling back to the beginning of the list when
we reached the last one) and selects it with the
orxLocale_SelectLanguage() function.
When doing so, all created orxTEXT
objects will be automatically updated if they use a localized string.
We'll see how to do that below in the config description.
We can also catch any
language selection as done in our EventHandler callback.
基本上我们只是移动到下一个可用的语言(如果到
最后一个则循环到列表的开头)并通过orxLocale_SelectLanguage()函数选择它。
当我们这么做时,如果使用一个本地化字符串的已
创建orxTEXT对象将会被自动更新。我们会在下面的配置描述中看到如何实现。
我们可以在EventHandler回调函数中做捕捉任意语言的选择事件。
orxSTATUS orxFASTCALL
StandAlone::EventHandler(const orxEVENT *_pstEvent)
{
switch(_pstEvent->eID)
{
case orxLOCALE_EVENT_SELECT_LANGUAGE:
orxLOCALE_EVENT_PAYLOAD *pstPayload;
pstPayload =
(orxLOCALE_EVENT_PAYLOAD *)_pstEvent->pstPayload;
orxLOG("Switching to '%s'.", pstPayload->zLanguage);
break;
default:
break;
}
return
orxSTATUS_FAILURE;
}
As
you can see, we only track the orxLOCALE_EVENT_SELECT_LANGUAGE event
here so as to display which is the new selected language.
如你所见,我们只跟踪
orxLOCALE_EVENT_SELECT_LANGUAGE事件来显示新选择的语言。
We're now done with
the code part of this tutorial. Let's now have a look at the config.
现在我们完成了本教程代码的部分。让我们看看配
置吧。
First of all, as you
might have seen, we use different folder for different architectures.
In other words, the
tutorials for Mac OS X are in the /mac folder, the ones for Linux in the
/linux and so on. By default orx will look in the current folder to
find the main config file.
As we don't want to duplicate the config file
in all the architecture folders, we create a very simple one which
purpose is only to include the one that contains all the info and which
is in the parent folder.
首选,正如你已经见到的,我们使用对不同的平台使用不同的文件夹。
换而言之,Mac OS X的教程放在
/mac文件夹,Linux的教程放在/linux,以此类推。默认的情况下,orx会查找当前目录下的主配置文件。
为了避免在不同的平台下都有重复的配置文件,我
们创建了一个简单地配置文件包含了父文件夹中配置文件的全部信息。
Let's see how we do this by looking at the
content of 10_StandAlone.ini from one of the sub-folder (ie. one that is
stored in the same folder than the tutorial executable).
让我们通过其中一个子目录(即和教程可执行文件
存储位置相同的文件夹)下的10_StandAlone.ini 的内容看看怎么实现的。
@../10_StandAlone.ini@
That's all we can find
in it. As you can see in the
template files
, we can include other
config files by writing @path/to/FileToInclude@.
所有的内容如上。就像你在
配置语法说明
中看到的,我们可以通过@path/to
/FileToInclude@ 在一个配置文件中包含其他的配置文件。(译注:这里怀疑是作者的问题,template
files实际是想链接到WIKI的配置的说明上去)
Let's now have a look at the config file
which is stored in the parent folder (ie.
../10_StandAlone.ini
).
现在我们看看父文件夹中存储的配置文件(即
../10_StandAlone.ini
link):
First let's define our
display.
首先定义我们的display配置段:
[Display]
ScreenWidth = 800
ScreenHeight = 600
Title = Stand
Alone/Locale Tutorial
As you can see, we're creating a window of
resolution 800×600 and define its title.
如你所见,我们将要创建一个分辨率为
800×600的窗口,并且定义了他的标题。
We now need to provide info for our viewport
and camera.
现在我们要提供视口(viewport)和摄像
头(camera)配置段的信息:
[Viewport]
Camera =
Camera
BackgroundColor
= (20, 10, 10)
[Camera]
FrustumWidth = @Display.ScreenWidth
FrustumHeight =
@Display.ScreenHeight
FrustumFar = 2.0
Position = (0.0,
0.0, -1.0)
Nothing new here as
everything was already covered in the
viewport tutorial
.
与我们在
视口教程
(viewport
tutorial)中提到的没有任何区别。
Let's now see which inputs are defined.
现在看看输入(inputs)是怎么定义的:
[Input]
SetList = MainInput
[MainInput]
KEY_ESCAPE = Quit
KEY_SPACE =
CycleLanguage
MOUSE_LEFT = CycleLanguage
In the Input section, we define all our
input sets. In this tutorial we'll only use one called MainInput but we
can define as many sets as we want (for example, one for the main menu,
one for in-game, etc…).
The MainInput sets contain 3 mapping:
- KEY_ESCAPE
will trigger the input named Quit
- KEY_SPACE
and MOUSE_LEFT will both trigger the input named CycleLanguage
在Input配置段,我们定义我们所有的输入集
合。本教程中我们只会用到一个叫做MainInput的集合,但我们也定义其他任意想要使用的集合(例如,主菜单一个,游戏中一个,等等)。
MainInput集合包括3个映射:
- KEY_ESCAPE
会触发名为Quit的输入
- KEY_SPACE 和 MOUSE_LEFT 都会触发名为CycleLanguage的输入
We can add as many
inputs we want in this section and bind them to keys, mouse buttons
(including wheel up/down), joystick buttons or even joystick axes.
我们可以在这个配置段加入任意多的输入集合并且
将它们绑定到按键、鼠标按钮(包括滚轮上/下)、游戏摇杆按钮甚至游戏摇杆的方向轴。
Let's now see how we
define languages that will be used by the orxLOCALE module.
现在让我们看看怎么定义将要被
orxLOCALE模块使用的语言。
[Locale]
LanguageList =
English#French#Spanish#German#Finnish#Swedish#Norwegian
[English]
Content = This is
orx's logo.
Lang
= (English)
[French]
Content = Ceci est le logo d'orx.
Lang = (Fran?ais)
[Spanish]
Content = Este es el
logotipo de orx.
Lang = (Espa?ol)
[German]
Content = Das ist orx Logo.
Lang = (Deutsch)
[Finnish]
Content = T?m? on orx
logo.
Lang
= (Suomi)
[Swedish]
Content = Detta ?r orx logotyp.
Lang = (Svenska)
[Norwegian]
Content = Dette er orx
logo.
Lang
= (Norsk)
To
define languages for localization we only need to define a Locale
section and define a Language List that will contain all the languages
we need.
After
that we need to define one section per language and for every needed
keys (here Content and Lang) we set their localized text.
为了定义本地化需要的语言我们要定义一个
Locale配置段和一个包含我们所需要全部的语言的列表。
As the localization system in based on orx's
config one, we can use its inheritance capacity for easily adding new
languages to the list (in another extern file, for example), or even for
completing languages that have been partially defined.
由于本地化系统是基于orx配置部分,我们可以
使用它的继承能力来简化把语言加入列表的过程(例如在另一个外部文件中),甚至也可以完善曾经只被部分定义的语言。
Let's now see how we
defined our Logo object.
现在看看我们是怎么定义Logo对象:
[LogoGraphic]
Texture =
../../data/object/orx.png
Pivot = center
[Logo]
Graphic =
LogoGraphic
FXList
= FadeIn # LoopFX # ColorCycle1
Smoothing = true
Again, everything we
can see here is already covered in the
object tutorial
.
又一次,所有的内容我们都已经在
对象教程
(object tutorial)中涵盖了。
If you're curious you
can look directly at
10_StandAlone.ini
to see which kind of
FXs we defined, but we won't cover them in detail here.
如果你对我们定义了哪些FX感到好奇,你可以直
接查看
10_StandAlone.ini
,但是我们不会在这里讨论它们的细节。
Next thing to check:
our Legend object.
下
一个要查看的是:我们的Legend对象:
[Legend]
ChildList = Legend1 # Legend2
Surprise! Actually
it's an empty object that will spawn two child objects: Legend1 and
Legend2.
奇怪吧!其实它只是一个我们由两子对象繁衍出来的空对象。
Code-wise we were
creating a single object called Legend but apparently we'll end up with
more than one object.
The same kind of technique can be used to
generated a whole group of objects, or a complete scenery for example,
without having to create them one by one code-wise.
It's even possible to
chain objects with ChildList and only create a single object in our code
and having hundreds of actual objects created.
However, we won't have
direct pointers on them, which means we won't be able to manipulate
them directly.
That being said, for all non-interactive/background object
it's usually not a problem.
Be also aware that their frames (cf.
frame tutorial
) will reflect the
hierarchy of the ChildList 'chaining'.
通过这样的编码方式,我们创建了一个叫做Legend的单独对象但显然最终将超过一
个对象。同样的技术也可以用来创建一组的对象,或者是完成一个场景,而不是一个一个创建。同样可以将对象通过ChildList串联起来并只在我们的代码
中创建一个单独的对象而同时有数百个对象被创建。
然而我们对它们不会有直接的指针,这意味着我们将不可能直接操作它们。
话虽这么说,对所有非交互/后台对象这却不是一
个问题。
同时
请注意他们的帧(参考:frame tutorial LINK)会影响ChildList ‘串联’的继承。
Ok, now let's get back
to our two object, Legend1 and Legend2.
好了,现在让我们回到Legend1
和Legend2两个对象。
[Legend1]
Graphic =
Legend1Graphic
Position = (0, 0.25, 0.0)
FXList =
ColorCycle2
ParentCamera
= Camera
[Legend2]
Graphic =
Legend2Graphic
Position = (0, 0.3, 0.0)
FXList =
@Legend1
ParentCamera
= @Legend1
They
look very basic, they're both using the same FX (ColorCyle2), they both
have a Position and each of them has its own Graphic.
它们看起来很基本,都是用了相同的
FX(ColorCyle2),它们也都有一个位置并且各自有它们的Graphic。
NB: We can also see
that we defined the ParentCamera attribute for both of them. This means
that their actual parent will become the camera and not the Legend
object in the end.
注
意:我们也可以看到我们为它们定义了ParentCamera属性。这意味着最终它们实际的父对象为Camera而不是Legend对象。
However Legend will
still remain their owner, which means that they'll automatically be
erased when Legend will be deleted.
然而Legend还将是它们的拥有者,这说明它们将会在Legend被销毁时自动被
销毁。
Let's now finish by
having a look at their Graphic objects.
现在让我们看一下它们的Graphic对象作为
结束。
[Legend1Text]
String = $Content
[Legend2Text]
String = $Lang
[Legend1Graphic]
Pivot = center
Text = Legend1Text
[Legend2Graphic]
Pivot = center
Text = Legend2Text
We can see that each
Graphic has its own Text attribute: Legend1Text and Legend2Text.
They both have a
different String.
The leading $ character indicates that we won't display a raw
text but that we'll use the content as a key for the localization
system.
So
in the end, the Legend1 object will display the localized string with
the key Content, and Legend2 the one which has the key Lang.
我们可以看到每一个Graphic都有自己的
Text属性:Legend1Text和Legend2Text。
它们都有不同的String字段。(译注:这里的String是指配置)
开头的$字符说明我们不会显示一个原始的文本但
内容作为我们将会把内容作为本地化系统的关键字。
所以在最后,Legend1对象会显示该关键字Content的本地化字符串,Legend2会
显示关键字Lang的本地化字符串。
Everytime
we will switch to another language, both orxTEXT objects (ie.
Legend1Text and Legend2Text) will have their content updated
automagically in the new selected language.
As we saw earlier, we can catch the
orxLOCALE_EVENT_SELECT_LANGUAGE event to do our own specific processing
in addition, if needed.
每一次我们会切换到另一个语言,所有的orxTEXT对象(例如Legend1Text和
Legend2Text)会根据新选择的语言自动更新它们的内容。:)
正如我们早前看到的,如果需要,我们还可以捕获
orxLOCALE_EVENT_SELECT_LANGUAGE事件来进行特定的处理。
Resources
资源
源代码:
10_StandAlone.cpp
配置文件:
10_StandAlone.ini
1)
即不再依靠ORX 启动器
2)
在orx.h中实现
3)
在这种情 况下预编译宏
__orxCPP__ 会被自动定义
4)
这些给出的参数并没有包含我们需要的用来决定主配置文件名的可执行文件的名字
5)
使用WinMain()替代main()
阅读全文....
本文译自
orx tutorials
的FX
。胡四娃
译最新版本见Orx
官方Wiki中文教程
。转载自:胡四娃的博客
。原文链接在http://www.cppblog.com/Husiwa/archive/2010/07/07/119534.aspx
。
综述
这篇教程介绍了什么是特效以及
如何创建它们
特效是将曲线及其组合而成的一组数据(正弦线、三角型边、矩形或者线性),应用在不同类型的参数
中。如:
缩放
、旋转、位置、速度、颜色等。
特效在配置文件中设置,仅仅只
需要一行代码就可以在对象上使用这些特效。
可以有最多8条任意类型的曲线组合在一起形成一个特效。
在同一时间,可以有最多4个特
效应用于同一个对象上面。
特效可以使用绝对值或者相对值,这取决于配置文件中Absolute标签。
控制曲线的周期、相位、和振幅
都是允许的。
对于位置和速度特效来说,输出值可以使用对象的方向 和/或
缩放值,以相对方式应用于对象目前的状态。
这也就允许我们创造极其拉风的视觉特效。
除非特效已经缓存在内存中,否
则特效参数全部在配置文件中进行调整,并且使用退格键来即时重载。 (cf.通过 ''KeepInCache'' 属性来实现内存的缓存).
比如说:你不能调整正在运行的
循环特效,因为他已经在默认的配置文件中定义好了。在这个测试程序运行的时候,所有其它的特效能够被更新。
通常说来,随机值的使用可以给
特效带来更多的变化。
比如, 晃动方式的缩放(the wobble scale), 带颜色的闪光(the
flash color) 和 攻击式的移动(the "attack" move) 等特效就使用了少量的随机值。
就像显示事件一样,我们也可以
注册特效的开始播放和停止的事件。
因为循环时间是永远不会停下来的,所以对应的停止事件
(''orxFX_EVENT_STOP'')永远不会发生.
们也会简单的介绍一下如何一些个性数据(仅仅包
含一个布尔值的结构)添加到orxOBJECT中。((九天注:这里作者有点穿越了,需要看下面的例子才能懂,作者定义了一个仅包含一个Bool值的结构
MyObject))
在事件的回调函数中,我们通过它,在特效开始的时候为对象加锁,在结束的时候解锁。
我们使用锁是为了让
soldier(士兵)在同一时刻只有一个特效在发挥作用。
把这些东西写在这里,仅仅具有教育意义。
((九天注:本来一个对象可以同时有4个特效发
生,这里作者仅仅是告诉你怎么使用“个性数据”才这样做的,所以说仅仅具有教育意义。))
详细内容
通常,我们先载入配置文件,创
建一个时钟,然后注册更新函数,最后,创建我们的soldier和盒对象。请在之前的教程中获取更多信息。
注册 输入和特效事件
orxEvent_AddHandler(orxEVENT_TYPE_FX, EventHandler);
orxEvent_AddHandler(orxEVENT_TYPE_INPUT, EventHandler);
大家可以看到,在这两个事件中,我们使用了同一个回调函数
(EventHandler).
现在我们迅速的扫一眼自己的“对象”数据结构。
typedef
struct
MyObject
{
orxBOOL bLock;
} MyObject;
接下来,看看如何用
orxObject_SetUserData()将它绑定到soldier上
MyObject *pstMyObject;
pstMyObject = orxMemory_Allocate(sizeof
(MyObject), orxMEMORY_TYPE_MAIN);
pstMyObject->bLock = orxFALSE;
orxObject_SetUserData(pstSoldier, pstMyObject);
现在看看如何在Update函数中使用特效
orxSTRING zSelectedFX;
if
(orxInput_IsActive("SelectWobble"
))
{
zSelectedFX = "WobbleFX"
;
}
else
if
(orxInput_IsActive("SelectCircle"
))
{
zSelectedFX = "CircleFX"
;
}
[...]
// Soldier not locked?
if
(!((MyObject *)orxObject_GetUserData(pstSoldier))->bLock)
{
if
(orxInput_IsActive("ApplyFX"
) && orxInput_HasNewStatus("ApplyFX"
))
{
orxObject_AddFX(pstSoldier, zSelectedFX);
}
}
可以看到,我们通过orxObject_GetUserData()这个函数得到了
我们想要的数据,向solder里添加特效的方法跟添加声音的方法如出一辙,用的都是这个函数orxObject_AddFX()。
接下来,看看EventHandler这个函数
首先是输入方面,这里只展示了每次输入时哪个按
键被使用了。
if
(_pstEvent->eType == orxEVENT_TYPE_INPUT)
{
if
(_pstEvent->eID == orxINPUT_EVENT_ON)
{
orxINPUT_EVENT_PAYLOAD *pstPayload;
pstPayload = (orxINPUT_EVENT_PAYLOAD *)_pstEvent->pstPayload;
if
(pstPayload->aeType[1
] != orxINPUT_TYPE_NONE)
{
orxLOG("[
%s
] triggered by '
%s
' + '
%s
'."
, pstPayload->zInputName, orxInput_GetBindingName(pstPayload->aeType[0
], pstPayload->aeID[0
]), orxInput_GetBindingName(pstPayload->aeType[1
], pstPayload->aeID[1
]));
}
else
{
orxLOG("[
%s
] triggered by '
%s
'."
, pstPayload->zInputName, orxInput_GetBindingName(pstPayload->aeType[0
], pstPayload->aeID[0
]));
}
}
}
正如你所见,我们通过按下的是一个单键还是一个组合键来判断展示不同的信息。
我们仅使用了两个首次输入点,因为我们知道,我
们的配置文件中没有超过两个的组合键。尽管orx支持最多四个组合键来做为一个单键。
orxInput_GetBindingName()
函数给了我们一个输入的文字显示。
注意:这些名称在配置文件中也绑定到了对应的按键上面。
现在来看下如何处理这个事件
if
(_pstEvent->eType == orxEVENT_TYPE_FX)
{
orxFX_EVENT_PAYLOAD *pstPayload;
orxOBJECT *pstObject;
pstPayload = _pstEvent->pstPayload;
pstObject = orxOBJECT(_pstEvent->hRecipient);
switch
(_pstEvent->eID)
{
case
orxFX_EVENT_START:
orxLOG("FX <
%s
>@<
%s
> has started!"
, pstPayload->zFXName, orxObject_GetName(pstObject));
if
(pstObject == pstSoldier)
{
// Locks it
((MyObject *)orxObject_GetUserData(pstObject))->bLock = orxTRUE;
}
break
;
case
orxSOUND_EVENT_STOP:
orxLOG("FX <
%s
>@<
%s
> has stoped!"
, pstPayload->zFXName, orxObject_GetName(pstObject));
if
(pstObject == pstSoldier)
{
// Unlocks it
((MyObject *)orxObject_GetUserData(pstObject))->bLock = orxFALSE;
}
break
;
}
}
在soldier上的动画开始
的时候,我们用自己的数据结构来锁定它,相应的,停止的时候解锁。
看完了代码部分,我们再去看看配置文件。
首先看个简单的特效
:盒子上旋转的特效。
[RotateLoopFX]
SlotList =
Rotate
Loop =
true
[Rotate]
Type =
rotation
StartTime =
0.0
EndTime =
2.0
Curve =
sine
Pow =
2.0
StartValue =
0
EndValue =
360
[Box]
FXList =
RotateLoopFX
看到了吧,特效是在它创建之初
直接应用在盒对象上面的,而不是在代码中。
RotateLoopFX包含仅包含一个时间段(Rotate)并且一直循环
(attribute Loop)
然后定义Rotates时间段。时间的单位都是秒,角度的单位都是度。
定义这个旋转动画的时候,我们使用了一个正弦曲
线,让他每两秒旋转360度。
下面看下我们的摇摆特效。
[WobbleFX]
SlotList =
Wobble
[Wobble]
Type =
scale
StartTime =
0.0
EndTime =
1.0
Period =
0.2
Curve =
sine
Amplification =
0.0
StartValue =
(1.0, 1.0, 1.0)
EndValue =
(2.0, 2.0, 1.0) ~ (6.0, 6.0, 1.0)
我们修改了scale属性,并赋予它一个
''StartValue''(开始值)和''EndValue''(结束值)。
他们都是用向量来表示的,如果不想使用任何
各向异性
的值(译者注:专业名
词anisotropic
(各向异性)去知道确切意思)的话,也可是使用
float类型来表示。
虽然看起来我们正在使用一个
isotropic(各向同性
)
((Z值不影响2D元素))的值,这个EndValue也 不过是一个随机值。
也就是说,它的X和Y部分可能是完全统统的随机值!
除此之外,我们使用了一个简单的周期为0.2秒
的正弦曲线,它将会播放1秒钟。
看到了吧,我们将Amplification(增幅)
的值设为0,这就是说,随着时间的推进,曲线的振幅会逐渐变低。
注意:默认的Amplification是1,表示不随时间变化,保持稳定,当值大于1时,振幅
就会加大;当值小于1时,振幅就会减少。
看看圆是如何运动的。
[CircleFX]
SlotList =
CircleX#CircleY
KeepInCache =
true
[CircleX]
Type =
position
StartTime =
0.0
EndTime =
1.0
Curve =
sine
StartValue =
(0.0, 0.0, 0.0)
EndValue =
(-50.0, 0.0, 0.0)
UseOrientation =
true
UseScale =
true
[CircleY@CircleX]
Phase =
0.25
StartValue =
(0.0, -25.0, 0.0)
EndValue =
(0.0, 25.0, 0.0)
我们使用两个时间段来控制它的位置,这样才能做
出一个圆形的运动。第一个时间段是CircleX,他将会应用在对象的X轴向的振幅。第二个时间段CircleY,会产生一个同样幅度的作用效果在Y轴
上。
如果我们不更改CircleY的相位,是不会发
生圆形的运动。
现在假设一个正弦曲线,在初始值
(''StartValue'')是相位0,准备增加
在相位0。25的时候,到达中间点,将会继续增加
在相位0.5的时候,到达最高值
(''EndValue''),准备下降
在
相位0.75的时候,回到中间点,继续下降
在相位1.0的时候,就跟相位0(''StartValue'')是一样的了
注意:这段描述正弦曲线的工作过程也同样适用于
三角形,但是却不适用于线形。
我
们将略过大多数其他的特效,因为那里没有什么我们不知道的新知识了。
但是我们还是要迅速的看一眼翻转的特效,他将会向我们展示如何翻转一个对象。就像Paper
Mario Wii((九天注:Wii上的[[wp>Paper
Mario|纸片马里奥]]是个很出名的游戏,作者的意思就是这里的flip描述的就是那个游戏里面的风格和效果))的风格.
[FlipFX]
SlotList =
Flip
[Flip@Wobble]
EndTime =
0.5
Period =
1.0
Amplification =
1.0
EndValue =
(-1.0, 1.0, 1.0)
看到了吧,我们很简单的使用负值完成了这个效果。
同时也注意到,我们给Period(周期)设了一个明确的值。
我们选了一个两倍于定义的正弦曲线的
Period,这样我们就只使用了正弦曲线的上升的那一半。同时,我们也将Amplification改 回了1。(在”“Wobble”“中被设为0)
资源
源代码:
07_FX.c
配置文件:
07_FX.ini
阅读全文....
本文译自 orx tutorials 的视口与摄像机
(viewport & camera)
落后的簔羽鹤译。最新版本见Orx 官方中文Wiki
。
本文转自落后的簔羽鹤的博客
。原文链接在:http://blog.csdn.net/wind2006/archive/2010/07/07/5717831.aspx
。望有新人能够加入这个翻译者的队伍,早日将Orx的WIKI页中文化。有兴趣的请加入qq群73063577,并与我取得联系,防止重复翻译。
综述
请阅读前面的基本教程
basic object creation(基本类创建),clock handling(时钟) ,fremes hierarchy(层次结构) 和
animations(动画)。
此教程显示了如何使用有多个摄像机的多视口技术。教程中将同时创建4个视口。
分别为左上角的(Viewport1),右下角的(Viewport4),它们共用一个摄像机
(Camera1),实现此功能,只需要在配置文件中配置2个视口的Camera属性,为同一个(也就是Camera1)。当我们使用鼠标的左右键旋转摄
像机(Camera1),left Control或left
Shift键+方向键进行摄像机的缩放操作,关联的两个Viewport1和Viewport4将相应的发生变化。
右上角视口(Viewport2)是基于另一个摄像机(Camrea2),此摄像机的视锥较第一
个窄,所以显示时比例是其的两倍大。在教程的程序中,我们不能通过任何操作设置此视口。
最后一个视口(Viewport3)是基于Camera3的,Camera3的配置与
Camera1完全一样。
NB:当两个视口重叠,较先创建的将显示在顶层。
最后,有一个固定不动的箱子和一个世界坐标随着鼠标实时移动的小兵,也就是说无论如何设置视口的
摄像机,无论鼠标在那个视口上移动,小兵在它所属的视口中,相对于鼠标在在屏幕中的位置移动。
在配置文件中使用随机关键字符‘~’,使的视口和基本对象的颜色和大小可以随机创建。
NB:摄像机将它的坐标/缩放尺度/旋转存放在
orxFRAME
结构中,在[[frame|frame]]教程中我们看到他们是orxFrame继承体系的一部分。另一方面Object应该置于其Camera所关联的
Viewport中。
详细说明
通常我们需要首先载入配置文件,创建时钟和注册回调的Update函数,最后创建主要的Object信息。关于实现的详情,请联系前面的教程。
虽然这次我们创建了4个视口,却没有什么新东西,仅仅是以下4行代码。
pstViewport =
orxViewport_CreateFromConfig("Viewport1");
orxViewport_CreateFromConfig("Viewport2");
orxViewport_CreateFromConfig("Viewport3");
orxViewport_CreateFromConfig("Viewport4");
正如你所看到的,我们只使用了
Viewport1的引用,以便后面进行操作。
让我们直接跳到Update函数的代码。
首先我们通过捕捉鼠标的坐标,
设置士兵的位置。我们已经在frame
tutorial里实现过了。这里我们做了一样的事情,但在4个视口中工作的都很完美。当鼠标离开视口时,世界坐标的指针,将被orxNull值所代替,
也就不会触发士兵的移动了。
orxVECTOR vPos;
if(orxRender_GetWorldPosition(orxMouse_GetPosition(&vPos),
&vPos) != orxNULL)
{
orxVECTOR
vSoldierPos;
orxObject_GetWorldPosition(pstSoldier, &vSoldierPos);
vPos.fZ =
vSoldierPos.fZ;
orxObject_SetPosition(pstSoldier,
&vPos);
}
在操作视口之前,我们先关注下视口所关联的摄像机,我们可以移动,旋转和缩放它。获
取摄像机的代码如下所示:
pstCamera =
orxViewport_GetCamera(pstViewport);
非常简单。让我们实现旋转。
((其他方向仅仅只有部分代码,但是逻辑是一样的)).
if(orxInput_IsActive("CameraRotateLeft"))
{
orxCamera_SetRotation(pstCamera, orxCamera_GetRotation(pstCamera) +
orx2F(-4.0f) * _pstClockInfo->fDT);
}
我们再次看到旋转的角度时间并
不依赖于FPS而且可以做时间的伸缩控制(译者注:比代码依然很简单。
现在让们来看看视口的数据配置情况。如慢动作)
因为用的是时钟的DT。
我
们再次看到旋转的角度时间并不依赖于FPS而是时钟的DT。我们也可以通过设置System这个配置选项来设置旋转速度,而不是使用硬编码。
实现缩放如下:
if(orxInput_IsActive("CameraZoomIn"))
{
orxCamera_SetZoom(pstCamera, orxCamera_GetZoom(pstCamera) *
orx2F(1.02f));
}
因为这个代码没有使用时钟信息,所以他将会被时钟频率和帧率所影响。
最后让我们移动摄像机。
orxCamera_GetPosition(pstCamera,
&vPos);
if(orxInput_IsActive("CameraRight"))
{
vPos.fX +=
orx2F(500) * _pstClockInfo->fDT;
}
orxCamera_SetPosition(pstCamera,
&vPos);
好
了,与摄像机有关的先到这里吧。
在下面的配置中我们将看到,同一个摄像机被连接到两个不同的视口。操作摄像机将同时
影响两个视口。
我们可以直接修改视口的位置和尺寸,如下所示:
orxFLOAT
fWidth, fHeight, fX, fY;
orxViewport_GetRelativeSize(pstViewport,
&fWidth, &fHeight);
if(orxInput_IsActive("ViewportScaleUp"))
{
fWidth *=
orx2F(1.02f);
fHeight*= orx2F(1.02f);
}
orxViewport_SetRelativeSize(pstViewport,
fWidth, fHeight);
orxViewport_GetPosition(pstViewport,
&fX, &fY);
if(orxInput_IsActive("ViewportRight"))
{
fX +=
orx2F(500) * _pstClockInfo->fDT;
}
orxViewport_SetPosition(pstViewport,
fX, fY);
如上 所示,没有什么惊奇的,非常简单。
让我们来接着看看
viewport的配置方面的东西。
[Viewport1]
Camera
= Camera1
RelativeSize = (0.5, 0.5, 0.0)
RelativePosition
= top left
BackgroundColor = (0, 100, 0) ~ (0, 255, 0)
[Viewport2]
Camera
= Camera2
RelativeSize = @Viewport1
RelativePosition
= top right
BackgroundColor = (100, 0, 0) ~ (255, 0, 0)
[Viewport3]
Camera
= Camera3
RelativeSize = @Viewport1
RelativePosition
= bottom left
BackgroundColor = (0, 0, 100) ~ (0, 0, 255)
[Viewport4]
Camera
= @Viewport1
RelativeSize = @Viewport1
RelativePosition
= bottom right
BackgroundColor = (255, 255, 0)#(0, 255,
255)#(255, 0, 255)
一共有3个摄像机,它们关联了4个视口,其中Camera1关联了
Viewport1和Viewport4。
我们注意到Viewport1的配置文件中relativeSize设置为
(0.5,0.5,0).它代表的意思在x轴和y轴方向上分别使用一半的显示尺寸(z轴被忽略)。也就是说,任何一个视口实际上显示部分的内容是可调的,
可以是全屏或者非全屏。
接下来我们注意到其他视口的RelativeSize属性被设置成@Viewport1。它的意
思是RelativeSize属性继承Viewport1的RelativeSize属性,也就是说它们的RelativeSize属性和
Viewport1的RelativeSize属性一样。我们也可以看到Viewport4的Camera属性被设置成@Viewport1,表明它继承
自Viewport1的摄像机。
为了避免视口在屏幕中互相重叠遮盖,我们可以设置RelativePosition属性为常量字
符或者使用vector(向量)设置它们的合理位置。
最后前三个视口使用随机的红色作为背景颜色,设置如下:
BackgroundColor
= (200, 0, 0) ~ (255, 0, 0)
如果我们希望通过准确的随机颜色进行设置,可以使用一下列表的形式设置,随机的颜色
分别为黄、青和品红,设置如下:
BackgroundColor = (255, 255, 0)#(0, 255,
255)#(255, 0, 255)
这种使用方式是相当于在三个颜色(黄色,蓝绿色,品红)中进行随机。
最后让我们关注摄像机的设置。
[Camera1]
FrustumWidth
= @Display.ScreenWidth
FrustumHeight = @Display.ScreenHeight
FrustumFar
= 1.0
FrustumNear = 0.0
Position
= (0.0, 0.0, -1.0)
[Camera2]
FrustumWidth
= 400.0
FrustumHeight = 300.0
FrustumFar
= 1.0
FrustumNear = 0.0
Position
= (0.0, 0.0, -1.0)
[Camera3@Camera1]
基本属性frustum(视
锥),被摄像机所拍摄的世界空间的一部分,将被映射到视口显示。
NB:使用2D摄像机视锥的形状是
长方体
。
我们可以发现Camera3完
全继承自Camera1,它没有覆盖Camera1的任何属性。
NB:使用完全继承所有属性可以写成:
[MySection@ParentSection]。
为什么实用两个不同的摄像头呢?仅仅因为可以有
两个不同的物理实体(physical entities):我们在代码中修改了Camera1的属性,而 Camara3将保持不变。
我们注意到Camera1的
FrustumWidth和FrustumHeight属性继承自Display的屏幕设置。
NB: 当继承某个属性,可以写成MyKey =
@ParentSection.ParentKey.当两个key一样时,其中父选关键字可以省略如:SameKey =
@ParentSection.
最后我们注意到Camera2具有较小的视锥。
也就是说Camera2只能看
到世界空间的较小部分。所以视口看起来具有了放大的效果。
资源
源代码:
05_Viewport.c
配置文件
:
05_Viewport.ini
1) very useful
for making HUD & UI, for example
在HUD和UI中很有用
2)
其他方向仅仅只有部分代码,但是逻辑是一样的
3) composed of
keywords top, bottom, center, right and left
由关键字
top,bottom,center,right和left组成
4) the '~' character is used as a
random operator between two numeric values
'~'
字符被用在两个数字之间,作为随机操作符
阅读全文....
本文译自 orx tutorials 的动画(Anim
)
。胡四娃译。最新版本见Orx 官方中文Wiki
。
本文转自胡四娃的博客
。原文链接在:http://www.cppblog.com/Husiwa/archive/2010/07/05/119366.html
望有新人能够加入这个翻译者的队伍,早日将Orx的WIKI页中文化。有兴趣的请加入qq群73063577,并与我取得联系,防止重复翻译。
综述
这篇教程只涉及了orx中最基本的动画使
用。
更多关于动画的东西在这里 猛击我.
图定义了动
画间所有可能的切换方式。动画通过一个唯一的字符串来引用。所有的切换和动画都是通过配置文件来创建的。
当一个动画被请求的时候,引擎会计算从当
前动画到请求动画之间的链路
如果这个链路存在,它会自动执行。用户将通过事件被告知动画何时开始、停止、删节或者循环。
如果我们不能具体
制定任何目标动画,引擎就会很自然的沿着属性中定义的线路(走下去)。
也有一个方法来越过这个寻路过程并且迅速的指向一个动画。
详细内容
通常,我们先载入config
file(配置文件),创建一个viewport,创建一个clock(时钟)并且注册Update(更新)函数,最后创建一个主对象。
请从之前
的教程中获得更多的信息。
现在我们开始从代码入手,我们将会从本页的底部看到数据是如何组织的。
在Update函数中,当输入
GoLeft激活的时候会触发WalkLeft动画;GoRight激活的时候会触发WalkRight函数.
当没有激活态的输入时,我们会移除
目标动画,让这个图保持一个自然的状态
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
if
(orxInput_IsActive("GoRight"
))
{
orxObject_SetTargetAnim(pstSoldier, "WalkRight"
);
}
else
if
(orxInput_IsActive("GoLeft"
))
{
orxObject_SetTargetAnim(pstSoldier, "WalkLeft"
);
}
else
{
orxObject_SetTargetAnim(pstSoldier, orxNULL);
}
就是这样!如何从任意当前动画切换到目标动画将会通过这个矢量图来计算。如果需要切换,他们将会自动播放。
注意:有很多的函数可以用高级的方法来控制动画,但是99%的时候,这两个函数是最常用的
(orxObject_SetCurrentAnim() 和 orxObject_SetTargetAnim())。
让
我们来看一下,动画是如何通知我们发生了什么的(比如,就像同步语音一样)。
首先,我们要向动画事件注册回调函数。
orxEvent_AddHandler(orxEVENT_TYPE_ANIM, EventHandler);
好了!让我们看下现在可以做什么了。
我们说我们想要打印出对象中哪个动画被播放、停止、剪切或者循环。需要写一下的回调函数。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
orxSTATUS orxFASTCALL EventHandler(const
orxEVENT *_pstEvent)
{
orxANIM_EVENT_PAYLOAD *pstPayload;
pstPayload = (orxANIM_EVENT_PAYLOAD *)_pstEvent->pstPayload;
switch
(_pstEvent->eID)
{
case
orxANIM_EVENT_START:
orxLOG("Animation <
%s
>@<
%s
> has started!"
, pstPayload->zAnimName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));
break
;
case
orxANIM_EVENT_STOP:
orxLOG("Animation <
%s
>@<
%s
> has stoped!"
, pstPayload->zAnimName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));
break
;
case
orxANIM_EVENT_CUT:
orxLOG("Animation <
%s
>@<
%s
> has been cut!"
, pstPayload->zAnimName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));
break
;
case
orxANIM_EVENT_LOOP:
orxLOG("Animation <
%s
>@<
%s
> has looped!"
, pstPayload->zAnimName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));
break
;
}
return
orxSTATUS_SUCCESS;
}
先得到了事件的payload指针,因为我们只是在这里传递动画事件,所以我们可以安全的将payload
转化为orxANIM_EVENT_PAYLOAD类型,它在 orxAnim.h中定义。
如果我们在不同的事件(译者注:原文是even
根据上下文推断是作者拼写错误)类型中调用了同一个回调函数,我们首先将会查看是否得到了一个动画事件,可以这样做:
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
if
(_pstEvent->eType == orxEVENT_TYPE_ANIM)
最后,事件接收者(_pstEvent→hRecipient)通常是播放动画的那个对象。将其用宏orxOBJECT()来转化为orOBJECT类型
的对象。
现在让我们来看一眼数据方面的东西吧。
首先,我们需要定义一个动画集,它将会包含指定对象的动画的整个矢量图。
动
画集在不会再内存中重复,并且它与矢量图相对应的多有动画和链路。
在上面这个例子中,我们又4个动画和10条可以用来切换的链路。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[AnimSet]
AnimationList =
IdleRight#WalkRight#IdleLeft#WalkLeft
LinkList =
IdleRightLoop#IdleRight2Left#IdleRight2WalkRight#WalkRightLoop#WalkRight2IdleRight#IdleLeftLoop#IdleLeft2Right#IdleLeft2WalkLeft#WalkLeftLoop#WalkLeft2IdleLeft
现在我们来开始定义动画!
在这之前,为了减少文章篇幅,我们将要使用orx 配置文件的集成特性。
先锚点的位置定义一项。
也许你
可能在对象教程中看到了锚点的相关知识,锚点的位置信息将会匹配世界中的对象。如果没有确定的话,将会把左上角做为默认值。
锚点可以通过语义关键
字来确定,如: top, bottom, center, left and right也可以通过实际的值来确定。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[Pivot]
Pivot =
(15.0, 31.0, 0.0)
现在,我们来定义从锚点继承过来的图像对象。在我们这个例子中,它是一个位图,,包含了对象中所有的帧。因此基本的属性就是
位图文件的名字和一帧的大小。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[FullGraphic@Pivot]
Texture =
../../data/anim/soldier_full.png
TextureSize =
(32, 32, 0)
创建帧的准备工作都做好了。
让我们定义所有都是right-oriented的动画。一共6个。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[AnimRight1@FullGraphic]
TextureCorner =
(0, 0, 0)
[AnimRight2@FullGraphic]
TextureCorner =
(0, 32, 0)
[AnimRight3@FullGraphic]
TextureCorner =
(0, 64, 0)
[AnimRight4@FullGraphic]
TextureCorner =
(32, 0, 0)
[AnimRight5@FullGraphic]
TextureCorner =
(32, 32, 0)
[AnimRight6@FullGraphic]
TextureCorner =
(32, 64, 0)
看到了吧,他们全都继承于FullGraphic,唯一能区分他们的属性就是TextureCorner.
好,我们已经定义完了所有的图形对象(他们载入的时候会转变为orxGraphic结构),下面定义动画本身。让我们从ideright动画开始说起,它
包含一个单帧并持续0.1秒。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[IdleRight]
KeyData1 =
AnimRight6
KeyDuration1 =
0.1
太简单了,来尝试下第二个:
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[WalkRight]
DefaultKeyDuration =
0.1
KeyData1 =
AnimRight1
KeyData2 =
AnimRight2
KeyData3 =
AnimRight3
KeyData4 =
AnimRight4
KeyData5 =
AnimRight5
KeyData6 =
AnimRight6
当我们使用DefaultKeyDuration属性同时为所有的帧定义时并不是很难。我们可以像idleright动画中所做的那样,通过一个确定的键
值来覆盖任意一帧。我们如法炮制做出left-oriented动画。通常我们使用翻转图形对象时,我们将会在代码运行中做这件事。但是那不是我们的目
的!让我们来用与前面那个完全不同的方法来实现它!只有链路没有提到了让我们添上它。基本的链路结构非常简单,我们指定源动画和目的动画。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[IdleRightLoop]
Source =
IdleRight
Destination =
IdleRight
这里,我们有跟之前一样的基本信息,但是多了一个immediate属性做为键值。这就是说,当我们处于IdleRight动画时,并且目标是
WalkRight,我们不必等到IdleRight完成,将直接完成这个动作,这就给了我们一个剪切动画的方法。
正如在代码中看到的一样。当我们已经开始行走的时候,没有显式的调用空闲动画,这是怎么做到的?看下从WalkRight到IdleRight的链路。
E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
[IdleRight2WalkRight]
Source =
IdleRight
Destination =
WalkRight
Property =
immediate
当我们再WalkRight状态并且移除了目标动画,引擎不得按照自然的路线走下去。这个意思是说,它会选
取高优先级的链路。默认的优先级是8,它的范围是0到15.在这里,优先级是9,也就是说当我们没有目标的时候,就会选取它。它将会带我们回到
IdleRight状态。这里也加了immdiate属性,这样,我们就不必等“走”这个循环完事再回到“空闲”
注意:这只是
一个非常基本的图,用来阐述基本的动画切换过程,但是这个系统的扩展性很高。比如假设这样一个场景:你想从坐的状态变为走的状态,中间没有别的过度。随着
游戏的开发,你可能觉得在这两个状态间加一个站立的状态会比较好。这时,你只需要再配置文件中添加这多出来的一步,而整个代码文件都不用更改。
资源:
源文件:
04_Anim.c
配置文件:
04_Anim.ini
阅读全文....