【HGE】绘图底层

HGE是基于DX8.0的二维游戏引擎,多年没有更新了。

而我们知道Dx8.0跟DX9.0C是不同层次的。其实基本绘图逻辑差别不是太大,只是性能方面肯定不在一个水平上面。

让我感觉很大困惑的是,HGE的绘图结构效率到底适不适合即时大型网络游戏渲染?因为它的绘图逻辑是基于以前的DX7.0的绘图思想。

先分析它的架构:

 1 (*
2 ** HGE Primitive type constants
3 *)

4 const
5 HGEPRIM_LINES = 2;
6 HGEPRIM_TRIPLES = 3;
7 HGEPRIM_QUADS = 4;
8
9 (*
10 ** HGE Vertex structure
11 *)

12 type
13 THGEVertex = record
14 X, Y: Single; // screen position
15 Z: Single; // Z-buffer depth 0..1
16 Col: Longword; // color
17 TX, TY: Single; // texture coordinates
18 end;
19 PHGEVertex = ^THGEVertex;
20 THGEVertexArray = array [0..MaxInt div 32 - 1] of THGEVertex;
21 PHGEVertexArray = ^THGEVertexArray;
22 TCustomVertex = packed record
23 x, y, z: single; // Position
24 rhw: single; // Reciprocal of homogeneous w
25 Col: Longword; // Vertex Color
26 tu, tv: single; // Texture coordinates
27 end;
28 PCustomVertex = ^TCustomVertex;
29
30 (*
31 ** HGE Triple structure三角形结构
32 *)

33 type
34 THGETriple = record
35 V: array [0..2] of THGEVertex;
36 Tex: ITexture;
37 Blend: Integer;
38 end;
39 PHGETriple = ^THGETriple;
40
41 (*
42 ** HGE Quad structure四边形结构
43 *)

44 type
45 THGEQuad = record
46 V: array [0..3] of THGEVertex;
47 Tex: ITexture;
48 Blend: Integer;
49 end;
50 PHGEQuad = ^THGEQuad;


FVF常量定义:

1 const
2 D3DFVF_HGEVERTEX = D3DFVF_XYZ or D3DFVF_DIFFUSE or D3DFVF_TEX1;
3 VertexDef = D3DFVF_XYZRHW or D3DFVF_DIFFUSE or D3DFVF_TEX1;
4 VERTEX_BUFFER_SIZE = 4000; //静态缓冲区的大小基本参数


上面这个过程在D3D编程里面,大家应该很熟悉了,定义顶点结构、定义FVF常量结构。

接着应该是创建顶点缓冲和索引缓冲,HGE定义的是静态缓冲,也就是说缓冲区是在显示卡内存里面的。

继续看它创建缓冲区的代码:

其实这部分是非常关键和重要的,直接影响到引擎的性能。

  1 function THGEImpl.InitLost: Boolean;  //接口的子类实现部分
2 var
3 Target: IInternalTarget;
4 PIndices: PWord;
5 N: Word;
6 I: Integer;
7 begin
8 Result := False;
9
10 // Store render target
11
12 FScreenSurf := nil;
13 FScreenDepth := nil;
14
15 {$IFDEF HGE_DX8}
16 FD3DDevice.GetRenderTarget(FScreenSurf);
17 {$ELSE}
18 FD3DDevice.GetRenderTarget(0,FScreenSurf);
19 {$ENDIF}
20 FD3DDevice.GetDepthStencilSurface(FScreenDepth);
21
22 for I := 0 to FTargets.Count - 1 do begin
23 Target := IInternalTarget(FTargets[I]);
24 Target.Lost;
25 end;
26
27 // Create Vertex buffer
28 {$IFDEF HGE_DX8}
29 if (Failed(FD3DDevice.CreateVertexBuffer(VERTEX_BUFFER_SIZE * SizeOf(THGEVertex),
30 D3DUSAGE_WRITEONLY,D3DFVF_HGEVERTEX,D3DPOOL_DEFAULT,FVB)))
31 {$ELSE}//这些是DX9部分
32 if (Failed(FD3DDevice.CreateVertexBuffer(VERTEX_BUFFER_SIZE * SizeOf(THGEVertex),
33 D3DUSAGE_WRITEONLY,D3DFVF_HGEVERTEX,D3DPOOL_DEFAULT,FVB,nil)))
34
35 //D3DUSAGE_WRITEONLY指定应用程序只能写缓存。它允许驱动程序分配最适合的内存地址作为写缓存。注意如果从创建好的这种缓存中读数据,将会返回错误信息。
36
37
38 {$ENDIF}
39 then begin
40 PostError('Can''t create D3D vertex buffer');
41 Exit;
42 end;
43
44 {$IFDEF HGE_DX8}
45 FD3DDevice.SetVertexShader(D3DFVF_HGEVERTEX);
46 FD3DDevice.SetStreamSource(0,FVB,SizeOf(THGEVertex));
47 {$ELSE}//这些是DX9部分
48 FD3DDevice.SetVertexShader(nil);
49 FD3DDevice.SetFVF(D3DFVF_HGEVERTEX);
50 FD3DDevice.SetStreamSource(0,FVB,0,SizeOf(THGEVertex));
51 {$ENDIF}
52
53 // Create and setup Index buffer
54
55 {$IFDEF HGE_DX8}
56 if (Failed(FD3DDevice.CreateIndexBuffer(VERTEX_BUFFER_SIZE * 6 div 4 * SizeOf(Word),
57 D3DUSAGE_WRITEONLY,D3DFMT_INDEX16,D3DPOOL_DEFAULT,FIB)))
58 {$ELSE}//这些是DX9部分
59 if (Failed(FD3DDevice.CreateIndexBuffer(VERTEX_BUFFER_SIZE * 6 div 4 * SizeOf(Word),
60 D3DUSAGE_WRITEONLY,D3DFMT_INDEX16,D3DPOOL_DEFAULT,FIB,nil)))
61 {$ENDIF}
62 then begin
63 PostError('Can''t create D3D index buffer');
64 Exit;
65 end;
66
67 N := 0;
68 {$IFDEF HGE_DX8}
69 if (Failed(FIB.Lock(0,0,PByte(PIndices),0))) then
70 {$ELSE}//这些是DX9部分
71 if (Failed(FIB.Lock(0,0,Pointer(PIndices),0))) then
72 {$ENDIF}
73 begin
74 PostError('Can''t lock D3D index buffer');
75 Exit;
76 end;
77
78 for I := 0 to (VERTEX_BUFFER_SIZE div 4) - 1 do begin
79 PIndices^ := N ; Inc(PIndices);
80 PIndices^ := N+1; Inc(PIndices);
81 PIndices^ := N+2; Inc(PIndices);
82 PIndices^ := N+2; Inc(PIndices);
83 PIndices^ := N+3; Inc(PIndices);
84 PIndices^ := N; Inc(PIndices);
85 Inc(N,4);
86 end;
87
88 FIB.Unlock;
89 {$IFDEF HGE_DX8}
90 FD3DDevice.SetIndices(FIB,0);
91 {$ELSE}//这些是DX9部分
92 FD3DDevice.SetIndices(FIB);
93 {$ENDIF}
94
95 // Set common render states
96
97 //pD3DDevice->SetRenderState( D3DRS_LASTPIXEL, FALSE ); ignore this
98 FD3DDevice.SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE);
99 FD3DDevice.SetRenderState(D3DRS_LIGHTING,0);
100
101 FD3DDevice.SetRenderState(D3DRS_ALPHABLENDENABLE,1);
102 FD3DDevice.SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
103 FD3DDevice.SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
104
105 FD3DDevice.SetRenderState(D3DRS_ALPHATESTENABLE,1);
106 FD3DDevice.SetRenderState(D3DRS_ALPHAREF,1);
107 FD3DDevice.SetRenderState(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL);
108
109 FD3DDevice.SetTextureStageState(0,D3DTSS_COLOROP, D3DTOP_MODULATE);
110 FD3DDevice.SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
111 FD3DDevice.SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_DIFFUSE);
112
113 FD3DDevice.SetTextureStageState(0,D3DTSS_ALPHAOP, D3DTOP_MODULATE);
114 FD3DDevice.SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_TEXTURE);
115 FD3DDevice.SetTextureStageState(0,D3DTSS_ALPHAARG2,D3DTA_DIFFUSE);
116
117 {$IFDEF HGE_DX8}
118 FD3DDevice.SetTextureStageState(0,D3DTSS_MIPFILTER, D3DTEXF_POINT);
119 {$ELSE}//这些是DX9部分
120 FD3DDevice.SetSamplerState(0,D3DSAMP_MIPFILTER, D3DTEXF_POINT);
121 {$ENDIF}
122
123 if (FTextureFilter) then begin
124 {$IFDEF HGE_DX8}
125 FD3DDevice.SetTextureStageState(0,D3DTSS_MAGFILTER,D3DTEXF_LINEAR);
126 FD3DDevice.SetTextureStageState(0,D3DTSS_MINFILTER,D3DTEXF_LINEAR);
127 {$ELSE}//这些是DX9部分
128 FD3DDevice.SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
129 FD3DDevice.SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
130 {$ENDIF}
131 end else begin
132 {$IFDEF HGE_DX8}
133 FD3DDevice.SetTextureStageState(0,D3DTSS_MAGFILTER,D3DTEXF_POINT);
134 FD3DDevice.SetTextureStageState(0,D3DTSS_MINFILTER,D3DTEXF_POINT);
135 {$ELSE}//这些是DX9部分
136 FD3DDevice.SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_POINT);
137 FD3DDevice.SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_POINT);
138 {$ENDIF}
139 end;
140
141 FPrim := 0;
142 FCurPrimType := HGEPRIM_QUADS;
143 FCurBlendMode := BLEND_DEFAULT;
144 FCurTexture := nil;
145
146 FD3DDevice.SetTransform(D3DTS_VIEW,FMatView);
147 FD3DDevice.SetTransform(D3DTS_PROJECTION,FMatProj);
148
149 Result := True;
150 end;


这份代码是HGE包含DX9的,其实很不好,DX9跟DX8在一些方面是不兼容。

 显然顶点缓冲区是只写属性,因为最后是从索引缓冲里面读取数据。

这个顶点缓冲的大小是固定的,很难说够不够用。如果不够用怎么办?还没有看到这部分的代码在那里。

或者说,根本没有完全使用完整个顶点缓冲区的容量。

下面再看看他的代码:

 1 function THGEImpl.Gfx_BeginScene(const Target: ITarget): Boolean;
2 var
3 {$IFDEF HGE_DX8}
4 Surf, Depth: IDirect3DSurface8;
5 {$ELSE}
6 Surf, Depth: IDirect3DSurface9;
7 {$ENDIF}
8 HR: HResult;
9 begin
10 Result := False;
11
12 HR := FD3DDevice.TestCooperativeLevel;
13 if (HR = D3DERR_DEVICELOST) then
14 Exit;
15 if (HR = D3DERR_DEVICENOTRESET) then
16 if (not GfxRestore) then
17 Exit;
18
19 if Assigned(FVertArray) then begin
20 PostError('Gfx_BeginScene: Scene is already being rendered');
21 Exit;
22 end;
23
24 if (Target <> FCurTarget) then begin
25 if Assigned(Target) then begin
26 Target.Tex.Handle.GetSurfaceLevel(0,Surf);
27 Depth := (Target as IInternalTarget).Depth;
28 end else begin
29 Surf := FScreenSurf;
30 Depth := FScreenDepth;
31 end;
32
33 {$IFDEF HGE_DX8}
34 if (Failed(FD3DDevice.SetRenderTarget(Surf,Depth)))
35 {$ELSE}
36 if (Failed(FD3DDevice.SetRenderTarget(0,Surf)))
37 {$ENDIF}
38 then begin
39 PostError('Gfx_BeginScene: Can''t set render target');
40 Exit;
41 end;
42 if Assigned(Target) then begin
43 Surf := nil;
44 if Assigned((Target as IInternalTarget).Depth) then
45 FD3DDevice.SetRenderState(D3DRS_ZENABLE,D3DZB_TRUE)
46 else
47 FD3DDevice.SetRenderState(D3DRS_ZENABLE,D3DZB_FALSE);
48 SetProjectionMatrix(Target.Width,Target.Height);
49 end else begin
50 if (FZBuffer) then
51 FD3DDevice.SetRenderState(D3DRS_ZENABLE,D3DZB_TRUE)
52 else
53 FD3DDevice.SetRenderState(D3DRS_ZENABLE,D3DZB_FALSE);
54 SetProjectionMatrix(FScreenWidth,FScreenHeight);
55 end;
56
57 FD3DDevice.SetTransform(D3DTS_PROJECTION,FMatProj);
58 D3DXMatrixIdentity(FMatView);
59 FD3DDevice.SetTransform(D3DTS_VIEW,FMatView);
60
61 FCurTarget := Target;
62 end;
63 FD3DDevice.BeginScene;
64 {$IFDEF HGE_DX8}
65 FVB.Lock(0,0,PByte(FVertArray),0);
66 {$ELSE}
67 FVB.Lock(0,0,Pointer(FVertArray),0);
68 {$ENDIF}
69 Result := True;
70 end;


应该看到了,这个是HGE每一帧里面需要调用的开始渲染函数。

在每一帧开始的时候就锁定顶点缓冲,然后把数据拷贝进缓冲区里面。

那么我们了解到的情况是:经常对静态缓冲加解锁是不明智的,因为只有等驱动完成了所有挂起的命令之后才能返回该缓冲的指针。如果经常这样做,这会导致CPU和GPU很多不必要的同步,这样性能将会变得很差。

何况是每一帧开始之后才填充数据,这种方式跟以前的DDRAW7的绘图模式完全是一样的。

procedure THGEImpl.Gfx_EndScene;
begin
RenderBatch(True);
FD3DDevice.EndScene;
if (FCurTarget = nil) then
FD3DDevice.Present(nil,nil,0,nil);
end;


结束渲染之前调用了一次RenderBatch函数,并且传入参数为True。

看看这个函数的功能:

procedure THGEImpl.RenderBatch(const EndScene: Boolean);
begin
if Assigned(FVertArray) then begin
FVB.Unlock;
if (FPrim <> 0) then begin
case FCurPrimType of
HGEPRIM_QUADS:
{$IFDEF HGE_DX8}
          FD3DDevice.DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,FPrim shl 2,0,FPrim shl 1);
{$ELSE}
FD3DDevice.DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,FPrim shl 2,0,FPrim shl 1);
{$ENDIF}
HGEPRIM_TRIPLES:
FD3DDevice.DrawPrimitive(D3DPT_TRIANGLELIST,0,FPrim);
HGEPRIM_LINES:
FD3DDevice.DrawPrimitive(D3DPT_LINELIST,0,FPrim);
end;

FPrim := 0; //绘制完毕,清零了,好像不用累计
end;

if (EndScene) then //结束渲染之前执行
FVertArray := nil
else
{$IFDEF HGE_DX8}
FVB.Lock(0,0,PByte(FVertArray),0);
{$ELSE}
FVB.Lock(0,0,Pointer(FVertArray),0); //常规的做法,我们是使用一个无类型指针,这里他使用的是FVertArray这样的指针
{$ENDIF}
end;
end;

 

1 FVertArray: PHGEVertexArray;

这个函数是创建精灵的时候,需要调用的函数,也是其它单元经常调用的。

 1 procedure THGEImpl.Gfx_RenderQuad(const Quad: THGEQuad);
2 begin
3 if Assigned(FVertArray) then begin
4 if (FCurPrimType <> HGEPRIM_QUADS)
5 or (FPrim >= VERTEX_BUFFER_SIZE div HGEPRIM_QUADS)
6 or (FCurTexture <> Quad.Tex)
7 or (FCurBlendMode <> Quad.Blend)
8 then begin
9 RenderBatch;
10 FCurPrimType := HGEPRIM_QUADS;
11 if (FCurBlendMode <> Quad.Blend) then
12 SetBlendMode(Quad.Blend);
13 if (Quad.Tex <> FCurTexture) then begin
14 if Assigned(Quad.Tex) then
15 FD3DDevice.SetTexture(0,Quad.Tex.Handle)
16 else
17 FD3DDevice.SetTexture(0,nil);
18 FCurTexture := Quad.Tex;
19 end;
20 end;
21
22 Move(Quad.V,FVertArray[FPrim * HGEPRIM_QUADS],
23 SizeOf(THGEVertex) * HGEPRIM_QUADS);
24 Inc(FPrim);
25 end;
26 end;


这个函数的调用刚好处于开始渲染和结束渲染之间。Move把Quad的数据复制到顶点缓冲里面。要知道Quad结构的数据在被调用之前就已经填充好了。然后再复制入缓冲区里面。

就是说,从渲染开始到渲染结束,都是处于Lock锁定的状态下。所以很难看出HGE的绘图高效在那里。显然这样的渲染方式不适合大量绘制图元,更不用说应用于大型或者超大型网络游戏里面了。

因为锁定状态是按照默认锁定的,就是在GPU绘图这段时间里面,CPU就一直在等待之中,而且系统就处于挂起状态,如果是绘制大量图元呢,结果是可想而知的。

按照我们常规的逻辑,当引擎渲染开始之后,最理想的状态是,这个时候不必再去计算和处理各种数据或者是再锁定缓冲区去修改里面的数据,而是按照渲染序列,一次性批量地进行渲染。

说真的,我看了很久,看不出HGE能够胜任大型网络游戏的优点在那里。怎么看,都好像适合以前那些小型游戏开发。

网上的资料都是研究怎么去学习,还没有看到有人去研究它的实现代码结构方面。希望了解这个引擎的人说下。

 
好像也不对,看一段代码:

 1 hge->Gfx_BeginScene();     //开始渲染,LOCK锁定缓冲区
2 bgspr->Render(0,0); //第一次调用 FHGE.Gfx_RenderQuad(FQuad);
3
4 for(i=0;i<nObjects;i++)
5 {
6 pObjects[i].x+=pObjects[i].dx*dt;
7 if(pObjects[i].x>SCREEN_WIDTH || pObjects[i].x<0) pObjects[i].dx=-pObjects[i].dx;
8 pObjects[i].y+=pObjects[i].dy*dt;
9 if(pObjects[i].y>SCREEN_HEIGHT || pObjects[i].y<0) pObjects[i].dy=-pObjects[i].dy;
10 pObjects[i].scale+=pObjects[i].dscale*dt;
11 if(pObjects[i].scale>2 || pObjects[i].scale<0.5) pObjects[i].dscale=-pObjects[i].dscale;
12 pObjects[i].rot+=pObjects[i].drot*dt;
13
14 spr->SetColor(pObjects[i].color);
           //循环调用 FHGE.Gfx_RenderQuad(FQuad);
15 spr->RenderEx(pObjects[i].x, pObjects[i].y, pObjects[i].rot, pObjects[i].scale);
16 }
17
18 fnt->printf(7,7,"UP and DOWN to adjust number of hares: %d\nSPACE to change blending mode: %d\nFPS: %d", nObjects, nBlend, hge->Timer_GetFPS());
19 hge->Gfx_EndScene(); //解锁缓冲区,结束渲染


int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)  //程序入口

``````````````
fnt=new hgeFont("font2.fnt");
spr=new hgeSprite(tex,0,0,64,64);
spr->SetHotSpot(32,32); //在这里生成一个矩形





------------------------------------------

procedure THGESprite.Render(const X, Y: Single);
var
TempX1, TempY1, TempX2, TempY2: Single;
begin
TempX1 := X - FHotX;
TempY1 := Y - FHotY;
TempX2 := X + FWidth - FHotX;
TempY2 := Y + FHeight - FHotY;

FQuad.V[0].X := TempX1; FQuad.V[0].Y := TempY1;
FQuad.V[1].X := TempX2; FQuad.V[1].Y := TempY1;
FQuad.V[2].X := TempX2; FQuad.V[2].Y := TempY2;
FQuad.V[3].X := TempX1; FQuad.V[3].Y := TempY2;

FHGE.Gfx_RenderQuad(FQuad);
end;


procedure THGESprite.SetHotSpot(const X, Y: Single);
begin
FHotX := X;
FHotY := Y;
end;

 


显然在锁定缓冲区的同时,进行各种运算生成数据,然后批量地进行绘制图元。从流程可以看出来,第一次生成——就是说初次复制到缓冲区的数据是不被立即渲染,而是在第二次生成数据并且调用Gfx_RenderQuad这个函数的时候,才被渲染。也就是上一次的数据放到下一次进行渲染,这样形成了一个延迟渲染的流程。显然这些针对的是批量渲染算法。

 

显然它只需要锁定一次缓冲区,就可以批量地绘制大量的图元,同时它不需要等到把所有的数据填充入缓冲区之后,再批量地绘制。好像比较适合数据量大的情况,当然这些对于绘制二维图元来说,应该说是足够了。

解决方案在另一个帖子里面。

转载于:https://www.cnblogs.com/GameDelphi/archive/2012/02/18/HGE.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ ※※※※ LEGEND for HGE绘图版本 更新日期:2019-01-31 ※※※※※※※※※※※※※※※※※※※※ ※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※ ※※※※ 注意:本次由于扩展数据类型,正在开区的请勿直接替换否则会导致数据混乱! ※※※※※※※ ※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※ ※※※※ Development QQ:8302775 LEGEND开发群:715347659 ※※※※※※※※※※※※※※※※※※ ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.解决小退会不定时黑屏,有时时间长有时时间段黑屏后与服务器彻底失去连接. 2.小退弹出对话框时CPU使用率会暴涨. 3.优化内核内存不释放问题. 4.修改内核字体改为微妙字体,支持繁体字和特殊符号. 5.修复人物死亡灰度,99%接近盛大的颜色. 6.修复行会功能不能使用的问题. 7.修复游戏命令打不开的功能. 8.完善六个经络界面点击不变换的问题. 9.修复查看别人装备栏显示错误. 10.解决发送屏幕公告时内存泄露导致小腿黑屏. 11.修复切换窗口和全屏时地图变黑. 12.修复WIN XP下查看物品属性时窗口花屏的问题. 13.修复六格英雄内功页树状选择文字显示混乱. 14.修改选择Edit框复制粘贴时默认文字为绿色. 15.取消数字显血显示人物等级功能,因为此功能查看别人时显示为0级. 16.重新整理客户端GUI输出,并完善绘图无法调试{源码问题,与程序无关}. 17.修复六格界面英雄状态栏从基础切换内功时字体全部变化的问题. 18.优化连击内存释放时间,减少物理内存的使用率. 19.自动寻路功能使用说明:打开小地图,用鼠标滑轮再小地图选择坐标就自动开始移动了. 20.修复聚灵珠有一颗再背包满经验,其它空的也会跟着自动满的BUG. 21.修复摆摊时选择购买物品时鼠标点下没有效果. 22.M2添加重新加载【交易NPC管理、管理NPC、刷怪配置、怪物爆率】功能. 23.修复自动寻路小地图不显示绿色的寻路标记. 24.修复操作栏底部缺少一条像素移动时会出现漏光现象. 25.解决小地图移动到物品上面时被物品名字覆盖的问题. 26.请使用配套IPLocal.dll和qqwry.dat不然获取IP会出乱码. 27.修复返回角色选择页面时小地图依然开启问题. 28.M2添加心灵召唤功能,宝宝离开主人后重新按技能键即可召唤回来. 29.修复客户端武器和衣服自定义素材不显示的问题. 30.M2扩展英雄穿戴触发功能H.TakeOn和H.TakeOff. ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 名称: 主体和英雄穿取装备触发脚本 功能:    穿上装备、取下装备时触发脚本 介绍:    脚本写在QFunction-0.txt中. 主体触发要对应标签为[@TakeOnX]、[@TakeOffX].其中X(0-12)是装备位置.在[@TakeOffX]中.可以检测该位置当前装备(也就是要取下的装备).在[@TakeOnX]中.可以检测到新戴上的装备 英雄触发要对应标签为[@H.TakeOnX]、[@H.TakeOffX].其中X(0-12)是装备位置.在[@H.TakeOffX]中.可以检测该位置当前装备(也就是要取下的装备).在[@H.TakeOnX]中.可以检测到新戴上的装备 装备位置说明:服装=0,武器=1,勋章=2,项链=3,头盔=4,左手镯=5,右手镯=6,左戒指=7,右戒指=8,护符=9,腰带=10,鞋子=11,宝石=12 脚本实例: ;========================================== [@TakeOn1] #If checkitemw 炼狱 1 #Act SendMsg 5 [主体]:戴上了炼狱威力无穷! ;========================================== ;========================================== [@TakeOff1] #If checkitemw 炼狱 1 #Act SendMsg 5 [主体]:炼狱取下来,你

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值