クロスプラットフォヌムの3Dゲヌム゚ンゞンを曞いたように

こんにちは、Habr 私たちの倚くは、おそらく「自分でゲヌムを曞いおみたせんか」ず思ったでしょう。 今、私は「Open Tomb 」プロゞェクトを実行しおいたす。「Tomb Raider」の最初の5郚でプレむするポヌタブル゚ンゞンを䜜成する詊みです。これはsourceforge.comで入手できたす。しかし、私自身の経隓からするず、これはれロから䜜成されたものであり、この分野の知識はほずんどありたせん。 今でも、倚くの知識では䞍十分であり、より良いこずやより正確に䜕かをするためのモチベヌションが足りない堎合もありたすが、䞀歩䞀歩プロゞェクトがどのように実珟したかを説明する方が良いでしょう。



゚ンゞンを曞くようになったきっかけ


物語はかなり前に始たり、Vista 64で玠晎らしいロゞックパズル「プッシャヌ」をプレむしたかったずいう事実から始たりたした。しかし、元のゲヌムは16ビットであり、開始を完党に拒吊したした。 Cでクロヌンを曞くこずほど良いこずはありたせんでした最初はより良い結果に぀ながる最良の゜リュヌションではないこずもありたす。 しばらくしお、 SDL v1.2 + OpenGLプラットフォヌムでゲヌムを実装したした。



画像



マップ転送の利䟿性のために、レベル゚ディタヌを远加し、64枚のカヌドすべおを手動で耇補したした。 しばらくしお、「プッシャヌ」ぞの関心が薄れ、私はすでに3dfxグラフィックスでトゥヌムレむダヌ 1を運転したいず思っおいたした。 そしお、倚くの人がすでに掚枬しおいるように、これを行うための既存の解決策はむしろ、䞻芳的にさえ私を本圓に喜ばせず、ポヌトを探し始めたした。 有名なOpen raiderプロゞェクトに加えお、䜕も芋぀かりたせんでした。 私はmingwコンパむラを䜿甚しおWindowsでビルドしたこずで苊しんだこずを今でも芚えおいたす開発環境、 コヌド::ブロックたたはnetbeansのいずれかを芚えおいたせん。 ビルド結果はたったく私を喜ばせたせんでした玄1分間レベルをロヌドし、最埌に黒い画面を衚瀺したした。 他の人のコヌドを遞択し、その構造ず関数の意味を理解する胜力はありたせんでした。 「より良い」をたずめる詊みは停止したした。 ただし、オヌプン゚ンゞンの少なくずも1぀を手動でアセンブルするずいうアむデアを埗たした。DieGNU Autotoolsの auto-configではなく、開発環境の自己アセンブルされたデザむナヌからです。



したがっお、モニタヌの埌ろにかなりの時間を眮いた埌、䞀定量のマットなどを眮いお、音のないQuake Tenebraeを組み立おたした 。 しかし、圌は働いた 私は他の人のコヌドをよりよく理解し始め、最終的にコンパむラの組織の少なくずも䜕かを理解し始めたした-それなしでは䞍可胜です。 その埌、いく぀かのマむナヌな改善が行われ、いく぀かのバグが修正され、サりンドが開始されたしたが、プロゞェクトはむンタヌネットにアップロヌドされたせんでしたそれでも、特にダヌクプレヌス゚ンゞンの存圚を考慮するず、道埳的に廃止されたした。 ただし、 Quake Tenebrae゚ンゞンコヌドから、ゲヌム党䜓の䜜業、個々のコンポヌネント、およびメモリマネヌゞャヌがどのように線成されおいるかを孊びたした非垞に単玔ではありたすが、 realloc関数を远加したしたが、クラッシュするこずなく機胜したした。



゚ンゞンを曞く


少し慣れるず、゚ンゞンをれロから曞き始めるこずにしたした。 楜しみず自己啓発のためだけに。 ゚ンゞンの䜜成の基瀎は次のずおりです。GCC-TDMv4.X.X + msysコンパむラヌおよびNetbeans開発環境。 ラむブラリ SDL v1.2 + OpenGL 実装された最初の機胜は、スクリヌンショットを撮り、*。bmpファむルに保存するこずで、この圢匏で動䜜するように自己蚘述ラむブラリを䜿甚しおいたした。 コマンドチヌトを入力し、テキストを衚瀺するためのコン゜ヌルなしでどの゚ンゞンができるかはおそらくないので、次に勉匷したのはOpenGLりィンドりにテキストを衚瀺し、 freetype 1 + gltt bunchを遞択する方法でした 。 最初の認識可胜なコマンドは終了コマンドであり、その埌、フォントサむズ、行などで再生するコマンドです... 参考文字列を解析し、トヌクンに順番に分割するためにQuake Iで䜿甚されおいるコヌドが奜きでした。



char *parse_token(char *data, char *token) { int c; int len; len = 0; token[0] = 0; if(!data) { return NULL; } // skip whitespace skipwhite: while((c = *data) <= ' ') { if(c == 0) return NULL; // end of file; data++; } // skip // comments if (c=='/' && data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } // handle quoted strings specially if (c == '\"') { data++; while (1) { c = *data++; if (c=='\"' || !c) { token[len] = 0; return data; } token[len] = c; len++; } } // parse single characters if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') { token[len] = c; len++; token[len] = 0; return data+1; } // parse a regular word do { token[len] = c; data++; len++; c = *data; if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') { break; } } while (c>32); token[len] = 0; return data; }
      
      







先に進むスクリプトのサポヌトが必芁になったずき、DOOM 3゚ンゞンの䟋を䜿甚しおID゜フトりェアのように゚ンゞンを開発するアプロヌチを䜿甚するこずにしたした。これはHabré=でここで非垞によく説明されたした。翻蚳ずそれに興味深いコメントを曞いた人。 この蚘事に感銘を受け、ゲヌム内のスクリプト䜜成のニヌズだけでなく、構成ファむルずコン゜ヌルコマンドの解析にもLUAを゚ンゞンに実装するこずにしたした぀たり、どこでも統䞀されたシステムが䜿甚されおいたす。 このアプロヌチは完党に成果を䞊げたした。



3Dに移りたしょう


研究所では、線圢代数、行列倉​​換、ベクトル、数倀法が奜きだったのは幞運でした。 これらの基本がなければ、゚ンゞンのプログラミングをれロから始めるのは非垞に無謀ですただし、これらのセクションを䟋で研究するために特定の理論的知識ベヌスがなければ、これはほずんど珟実的ではありたせん。 A.ボレスコフの著曞「OPENGLに基づいた3次元コンピュヌタヌゲヌムのグラフィックス」は、グラフィックスをマスタヌするのに倧いに圹立ちたした。 数孊的装眮、レンダラヌのタむプ、゚ンゞンの構造に慣れるために、私はそれを䜕床も読み盎したした。 シヌンを構築する原理ず皮マトリックスず投圱マトリックスの目的の抂念がなければ、進歩は䞍可胜です。 むンタヌネット䞊のいく぀かの資料ず単なる文献を研究した埌、私はポヌタルレンダラヌを実行するこずにしたした。 ゚ンゞンに最初に実装されたのは、自由に飛行するカメラず、互いにしか芋えないいく぀かのポヌタルでした。



ハヌドコヌドの埌、3぀の郚屋のワむダヌフレヌムシヌンが远加されたしたさらに2぀ず廊䞋。 しかし、そのような原始的な䞖界を飛び回るこずは本圓に興味深いのでしょうか...そしお、 Open Raiderのリ゜ヌスレむザヌを䜿甚しおレベルをレンダリングするこずにしたした。 結果は私を喜ばせ、ここで少なくずも最初の郚分で、 トゥヌムレむダヌをプレむするためのポヌトを䜜成する蚈画を実装するこずが最終的に決定されたした。 オブゞェクトを空間に配眮するために、OpenGLマトリックスを䜿甚したした。これにより、オブゞェクトのロヌカル座暙系の基本ベクトルにアクセスし、1぀のコマンドglMultMatrixf 倉換でオブゞェクトの䜍眮を蚭定し、1぀のsetFromOpenGLMatrix 倉換コマンドでオブゞェクトの方向を蚭定したす少し埌で。 以䞋は、䞊蚘のリンクのOpenGLマトリックス構造の画像です。



画像



゚ンゞンの倉曎履歎ずバックアップの可胜性を維持するために、たた自己開発のためだけに、 氎銀バヌゞョン管理システムを䜿甚するこずが決定されたした。 そのアプリケヌションにより、コヌドの蚘述の進行状況を远跡できるだけでなく、結果をsourceforge.comにアップロヌドするこずも可胜になりたした。 実際のマップからのポヌタルに関する情報が゚ンゞンに読み蟌たれ始めたずき、ポヌタルシステムの実装における膚倧な数の欠陥がすぐに衚面化したこずに泚意する必芁がありたす。 オブゞェクトの消倱やクラッシュの凊理には倚くの時間がかかりたしたが、今でもポヌタルモゞュヌルには重倧な修正が必芁だず思いたす。 ゚ンゞンレンダラヌは、カメラの䜍眮ず向きに応じお、郚屋のポヌタルを通過し始め、衚瀺されおいる郚屋のみをリストに远加したす。 次に、レンダラヌは、リストからルヌムずそのコンテンツを描画したす。 倧芏暡なオヌプンスペヌスの堎合、このアプロヌチが最も成功しおいるわけではないこずは明らかですが、プロゞェクトの目的には十分です。 再垰的ルヌムトラバヌサル関数の䟋を次に瀺したす。



 ** * The reccursion algorithm: go through the rooms with portal - frustum occlusion test * @portal - we entered to the room through that portal * @frus - frustum that intersects the portal * @return number of added rooms */ int Render_ProcessRoom(struct portal_s *portal, struct frustum_s *frus) { int ret = 0, i; room_p room = portal->dest_room; //    room_p src_room = portal->current_room; //    portal_p p; //      - frustum_p gen_frus; //    if((src_room == NULL) || !src_room->active || (room == NULL) || !room->active) { return 0; } p = room->portals; for(i=0; i<room->portal_count; i++,p++) //      { if((p->dest_room->active) && (p->dest_room != src_room)) //      { gen_frus = Portal_FrustumIntersect(p, frus, &renderer); //  -  .    if(NULL != gen_frus) //        { ret++; Render_AddRoom(p->dest_room); Render_ProcessRoom(p, gen_frus); } } } return ret; }
      
      







骚栌アニメヌションモデル


そしお、プロゞェクトで最も骚の折れる䜜業の1぀が始たりたした。 静的オブゞェクトを䜿甚した静的ルヌムのレンダリングは比范的簡単でしたが、アニメヌション化された骚栌モデルに関しおは...最初に気付いたのは、 Open Raiderリ゜ヌスロヌダヌが骚栌モデルに関するすべおの必芁な情報をロヌドしないこずです。 アニメヌションのフレヌム数が正しく決定されおいないため、1぀のアニメヌションに耇数のフレヌムが同時に含たれおいたす。

これらの問題を解決しようずしおいる間に、私はトゥヌムレむダヌレベルのフォヌマットず、同時に独自のリ゜ヌスロヌダヌを備えたvtプロゞェクトに関するさたざたなドキュメントを芋぀けたした。 このプロゞェクトには、モデルアニメヌションのロヌドフレヌムはありたせんでしたが、より構造化されたコヌドが含たれおいお、読みやすく、頭に浮かぶのに䟿利です。 そこで、プロゞェクトのロヌダヌをvtに眮き換えたした。 たずえば、 オヌプンレむダヌでは、 トゥヌムレむダヌの 5぀の郚分すべおに1぀の長い関数がロヌドされ、倚数のifずスむッチがゲヌムのバヌゞョン番号によっお切り替わりたす。これにより、コヌドの読み取りず゚ラヌの怜出が倧幅に耇雑になりたす。 vtには5぀のモゞュヌルがあり、それぞれがレベルの独自のバヌゞョンを担圓しおいたため、コヌドは読みやすく、倉曎は難しくありたせんでした。



アニメヌションの䞻な問題は、骚栌モデルの骚の回転角床の抜出でした。 実際には、スペヌスを節玄するために、コヌナヌは2〜4バむト単䜍でバむトコヌドに栌玍されおいたした。 最初の2バむトには、ここでどの軞の呚りに1タヌンがあるか、たたは3぀のコヌナヌ自䜓があるかどうかに関するフラグが含たれおいたす。 3タヌンの堎合、フラグずコヌナヌは4バむトで栌玍され、1タヌンの堎合は2バむトのみが䜿甚されたす。 さらに、すべおのモデル、アニメヌション、およびフレヌムの角床は1぀の配列に保存され、オフセットを蚈算する必芁がありたす。 さらに、モデルの個々のフレヌムのヘッダヌはこのバむトコヌドに保存されたたたであり、オフセットずの混乱が重芁ですが、フレヌム数が誀っお読み蟌たれるこずを远加したした埌で、フレヌム数が30 fpsの「補間」アニメヌションに䞎えられるこずがわかりたしたが、実際には、フレヌムは、1、1 / 2、1 / 3、1 / 4のファクタヌを持぀fpsの「スナップショット」圢匏で保存できたす。 アニメヌションフレヌムロヌダヌの完了埌、骚栌モデルは裏返しにならず、歪んだポリゎンから混乱に倉わりたした 次に、ララを「埩掻」させる必芁がありたす。 以䞋は、骚栌モデルを生成する関数のコヌドであり、デバッグ甚のコヌドのスペルずコメントアりトされたセクションが保存されたす。



 void GenSkeletalModel(struct world_s *world, size_t model_num, struct skeletal_model_s *model, class VT_Level *tr) { int i, j, k, l, l_start; tr_moveable_t *tr_moveable; tr_animation_t *tr_animation; uint32_t frame_offset, frame_step; uint16_t *frame, temp1, temp2; ///@FIXME: "frame" set, but not used float ang; btScalar rot[3]; bone_tag_p bone_tag; bone_frame_p bone_frame; mesh_tree_tag_p tree_tag; animation_frame_p anim; tr_moveable = &tr->moveables[model_num]; // original tr structure model->collision_map = (uint16_t*)malloc(model->mesh_count * sizeof(uint16_t)); model->collision_map_size = model->mesh_count; for(i=0;i<model->mesh_count;i++) { model->collision_map[i] = i; } model->mesh_tree = (mesh_tree_tag_p)malloc(model->mesh_count * sizeof(mesh_tree_tag_t)); tree_tag = model->mesh_tree; tree_tag->mesh2 = NULL; for(k=0;k<model->mesh_count;k++,tree_tag++) { tree_tag->mesh = model->mesh_offset + k; tree_tag->mesh2 = NULL; tree_tag->flag = 0x00; vec3_set_zero(tree_tag->offset); if(k == 0) { tree_tag->flag = 0x02; vec3_set_zero(tree_tag->offset); } else { uint32_t *tr_mesh_tree = tr->mesh_tree_data + tr_moveable->mesh_tree_index + (k-1)*4; tree_tag->flag = tr_mesh_tree[0]; tree_tag->offset[0] = (float)((int32_t)tr_mesh_tree[1]); tree_tag->offset[1] = (float)((int32_t)tr_mesh_tree[3]); tree_tag->offset[2] =-(float)((int32_t)tr_mesh_tree[2]); } } /* * ================= now, animation loading ======================== */ if(tr_moveable->animation_index < 0 || tr_moveable->animation_index >= tr->animations_count) { /* * model has no start offset and any animation */ model->animation_count = 1; model->animations = (animation_frame_p)malloc(sizeof(animation_frame_t)); model->animations->frames_count = 1; model->animations->frames = (bone_frame_p)malloc(model->animations->frames_count * sizeof(bone_frame_t)); bone_frame = model->animations->frames; model->animations->id = 0; model->animations->next_anim = NULL; model->animations->next_frame = 0; model->animations->state_change = NULL; model->animations->state_change_count = 0; model->animations->original_frame_rate = 1; bone_frame->bone_tag_count = model->mesh_count; bone_frame->bone_tags = (bone_tag_p)malloc(bone_frame->bone_tag_count * sizeof(bone_tag_t)); vec3_set_zero(bone_frame->pos); vec3_set_zero(bone_frame->move); bone_frame->v_Horizontal = 0.0; bone_frame->v_Vertical = 0.0; bone_frame->command = 0x00; for(k=0;k<bone_frame->bone_tag_count;k++) { tree_tag = model->mesh_tree + k; bone_tag = bone_frame->bone_tags + k; rot[0] = 0.0; rot[1] = 0.0; rot[2] = 0.0; vec4_SetTRRotations(bone_tag->qrotate, rot); vec3_copy(bone_tag->offset, tree_tag->offset); } return; } //Sys_DebugLog(LOG_FILENAME, "model = %d, anims = %d", tr_moveable->object_id, GetNumAnimationsForMoveable(tr, model_num)); model->animation_count = GetNumAnimationsForMoveable(tr, model_num); if(model->animation_count <= 0) { /* * the animation count must be >= 1 */ model->animation_count = 1; } /* * Ok, let us calculate animations; * there is no difficult: * - first 9 words are bounding box and frame offset coordinates. * - 10's word is a rotations count, must be equal to number of meshes in model. * BUT! only in TR1. In TR2 - TR5 after first 9 words begins next section. * - in the next follows rotation's data. one word - one rotation, if rotation is one-axis (one angle). * two words in 3-axis rotations (3 angles). angles are calculated with bit mask. */ model->animations = (animation_frame_p)malloc(model->animation_count * sizeof(animation_frame_t)); anim = model->animations; for(i=0;i<model->animation_count;i++,anim++) { tr_animation = &tr->animations[tr_moveable->animation_index+i]; frame_offset = tr_animation->frame_offset / 2; l_start = 0x09; if(tr->game_version == TR_I || tr->game_version == TR_I_DEMO || tr->game_version == TR_I_UB) { l_start = 0x0A; } frame_step = tr_animation->frame_size; //Sys_DebugLog(LOG_FILENAME, "frame_step = %d", frame_step); anim->id = i; anim->next_anim = NULL; anim->next_frame = 0; anim->original_frame_rate = tr_animation->frame_rate; anim->accel_hi = tr_animation->accel_hi; anim->accel_hi2 = tr_animation->accel_hi2; anim->accel_lo = tr_animation->accel_lo; anim->accel_lo2 = tr_animation->accel_lo2; anim->speed = tr_animation->speed; anim->speed2 = tr_animation->speed2; anim->anim_command = tr_animation->anim_command; anim->num_anim_commands = tr_animation->num_anim_commands; anim->state_id = tr_animation->state_id; anim->unknown = tr_animation->unknown; anim->unknown2 = tr_animation->unknown2; anim->frames_count = GetNumFramesForAnimation(tr, tr_moveable->animation_index+i); //Sys_DebugLog(LOG_FILENAME, "Anim[%d], %d", tr_moveable->animation_index, GetNumFramesForAnimation(tr, tr_moveable->animation_index)); // Parse AnimCommands // Max. amount of AnimCommands is 255, larger numbers are considered as 0. // See http://evpopov.com/dl/TR4format.html#Animations for details. if( (anim->num_anim_commands > 0) && (anim->num_anim_commands <= 255) ) { // Calculate current animation anim command block offset. int16_t *pointer = world->anim_commands + anim->anim_command; for(uint32_t count = 0; count < anim->num_anim_commands; count++, pointer++) { switch(*pointer) { case TR_ANIMCOMMAND_PLAYEFFECT: case TR_ANIMCOMMAND_PLAYSOUND: // Recalculate absolute frame number to relative. ///@FIXED: was unpredictable behavior. *(pointer + 1) -= tr_animation->frame_start; pointer += 2; break; case TR_ANIMCOMMAND_SETPOSITION: // Parse through 3 operands. pointer += 3; break; case TR_ANIMCOMMAND_JUMPDISTANCE: // Parse through 2 operands. pointer += 2; break; default: // All other commands have no operands. break; } } } if(anim->frames_count <= 0) { /* * number of animations must be >= 1, because frame contains base model offset */ anim->frames_count = 1; } anim->frames = (bone_frame_p)malloc(anim->frames_count * sizeof(bone_frame_t)); /* * let us begin to load animations */ bone_frame = anim->frames; frame = tr->frame_data + frame_offset; for(j=0;j<anim->frames_count;j++,bone_frame++,frame_offset+=frame_step) { frame = tr->frame_data + frame_offset; bone_frame->bone_tag_count = model->mesh_count; bone_frame->bone_tags = (bone_tag_p)malloc(model->mesh_count * sizeof(bone_tag_t)); vec3_set_zero(bone_frame->pos); vec3_set_zero(bone_frame->move); bone_frame->v_Horizontal = 0.0; bone_frame->v_Vertical = 0.0; bone_frame->command = 0x00; GetBFrameBB_Pos(tr, frame_offset, bone_frame); if(frame_offset < 0 || frame_offset >= tr->frame_data_size) { //Con_Printf("Bad frame offset"); for(k=0;k<bone_frame->bone_tag_count;k++) { tree_tag = model->mesh_tree + k; bone_tag = bone_frame->bone_tags + k; rot[0] = 0.0; rot[1] = 0.0; rot[2] = 0.0; vec4_SetTRRotations(bone_tag->qrotate, rot); vec3_copy(bone_tag->offset, tree_tag->offset); } } else { l = l_start; for(k=0;k<bone_frame->bone_tag_count;k++) { tree_tag = model->mesh_tree + k; bone_tag = bone_frame->bone_tags + k; rot[0] = 0.0; rot[1] = 0.0; rot[2] = 0.0; vec4_SetTRRotations(bone_tag->qrotate, rot); vec3_copy(bone_tag->offset, tree_tag->offset); switch(tr->game_version) { case TR_I: /* TR_I */ case TR_I_UB: case TR_I_DEMO: temp2 = tr->frame_data[frame_offset + l]; l ++; temp1 = tr->frame_data[frame_offset + l]; l ++; rot[0] = (float)((temp1 & 0x3ff0) >> 4); rot[2] =-(float)(((temp1 & 0x000f) << 6) | ((temp2 & 0xfc00) >> 10)); rot[1] = (float)(temp2 & 0x03ff); rot[0] *= 360.0 / 1024.0; rot[1] *= 360.0 / 1024.0; rot[2] *= 360.0 / 1024.0; vec4_SetTRRotations(bone_tag->qrotate, rot); break; default: /* TR_II + */ temp1 = tr->frame_data[frame_offset + l]; l ++; if(tr->game_version >= TR_IV) { ang = (float)(temp1 & 0x0fff); ang *= 360.0 / 4096.0; } else { ang = (float)(temp1 & 0x03ff); ang *= 360.0 / 1024.0; } switch (temp1 & 0xc000) { case 0x4000: // x only rot[0] = ang; rot[1] = 0; rot[2] = 0; vec4_SetTRRotations(bone_tag->qrotate, rot); break; case 0x8000: // y only rot[0] = 0; rot[1] = 0; rot[2] =-ang; vec4_SetTRRotations(bone_tag->qrotate, rot); break; case 0xc000: // z only rot[0] = 0; rot[1] = ang; rot[2] = 0; vec4_SetTRRotations(bone_tag->qrotate, rot); break; default: // all three temp2 = tr->frame_data[frame_offset + l]; rot[0] = (float)((temp1 & 0x3ff0) >> 4); rot[2] =-(float)(((temp1 & 0x000f) << 6) | ((temp2 & 0xfc00) >> 10)); rot[1] = (float)(temp2 & 0x03ff); rot[0] *= 360.0 / 1024.0; rot[1] *= 360.0 / 1024.0; rot[2] *= 360.0 / 1024.0; vec4_SetTRRotations(bone_tag->qrotate, rot); l ++; break; }; break; }; } } } } /* * Animations interpolation to 1/30 sec like in original. Needed for correct state change works. */ SkeletalModel_InterpolateFrames(model); GenerateAnimCommandsTransform(model); /* * state change's loading */ #if LOG_ANIM_DISPATCHES if(model->animation_count > 1) { Sys_DebugLog(LOG_FILENAME, "MODEL[%d], anims = %d", model_num, model->animation_count); } #endif anim = model->animations; for(i=0;i<model->animation_count;i++,anim++) { anim->state_change_count = 0; anim->state_change = NULL; tr_animation = &tr->animations[tr_moveable->animation_index+i]; j = (int)tr_animation->next_animation - (int)tr_moveable->animation_index; j &= 0x7fff; if(j >= 0 && j < model->animation_count) { anim->next_anim = model->animations + j; anim->next_frame = tr_animation->next_frame - tr->animations[tr_animation->next_animation].frame_start; anim->next_frame %= anim->next_anim->frames_count; if(anim->next_frame < 0) { anim->next_frame = 0; } #if LOG_ANIM_DISPATCHES Sys_DebugLog(LOG_FILENAME, "ANIM[%d], next_anim = %d, next_frame = %d", i, anim->next_anim->id, anim->next_frame); #endif } else { anim->next_anim = NULL; anim->next_frame = 0; } anim->state_change_count = 0; anim->state_change = NULL; if((tr_animation->num_state_changes > 0) && (model->animation_count > 1)) { state_change_p sch_p; #if LOG_ANIM_DISPATCHES Sys_DebugLog(LOG_FILENAME, "ANIM[%d], next_anim = %d, next_frame = %d", i, (anim->next_anim)?(anim->next_anim->id):(-1), anim->next_frame); #endif anim->state_change_count = tr_animation->num_state_changes; sch_p = anim->state_change = (state_change_p)malloc(tr_animation->num_state_changes * sizeof(state_change_t)); for(j=0;j<tr_animation->num_state_changes;j++,sch_p++) { tr_state_change_t *tr_sch; tr_sch = &tr->state_changes[j+tr_animation->state_change_offset]; sch_p->id = tr_sch->state_id; sch_p->anim_dispath = NULL; sch_p->anim_dispath_count = 0; for(l=0;l<tr_sch->num_anim_dispatches;l++) { tr_anim_dispatch_t *tr_adisp = &tr->anim_dispatches[tr_sch->anim_dispatch+l]; int next_anim = tr_adisp->next_animation & 0x7fff; int next_anim_ind = next_anim - (tr_moveable->animation_index & 0x7fff); if((next_anim_ind >= 0) &&(next_anim_ind < model->animation_count)) { sch_p->anim_dispath_count++; sch_p->anim_dispath = (anim_dispath_p)realloc(sch_p->anim_dispath, sch_p->anim_dispath_count * sizeof(anim_dispath_t)); anim_dispath_p adsp = sch_p->anim_dispath + sch_p->anim_dispath_count - 1; int next_frames_count = model->animations[next_anim - tr_moveable->animation_index].frames_count; int next_frame = tr_adisp->next_frame - tr->animations[next_anim].frame_start; int low = tr_adisp->low - tr_animation->frame_start; int high = tr_adisp->high - tr_animation->frame_start; adsp->frame_low = low % anim->frames_count; adsp->frame_high = (high - 1) % anim->frames_count; adsp->next_anim = next_anim - tr_moveable->animation_index; adsp->next_frame = next_frame % next_frames_count; #if LOG_ANIM_DISPATCHES Sys_DebugLog(LOG_FILENAME, "anim_disp[%d], frames_count = %d: interval[%d.. %d], next_anim = %d, next_frame = %d", l, anim->frames_count, adsp->frame_low, adsp->frame_high, adsp->next_anim, adsp->next_frame); #endif } } } } } }
      
      







おでかけ


骚栌モデルが機胜し始めたずき、物理孊の存圚を必芁ずするレベリングず「埩掻」に進むこずがすでに可胜でした。 そもそも、トピックに粟通し、既成の補品の遞択により培底的にアプロヌチするために、独自の物理゚ンゞンを䜜成するこずが決定されたした。 キャラクタヌコントロヌラヌを䜜成するために最初に必芁なこずは、高さを決定するこずです。 最初に、 重心アルゎリズムに基づいお䞉角圢ず光線の亀差を決定する関数が蚘述されたした。 その埌、移動セグメント、䞉角圢ず球、䞉角圢ず䞉角圢の亀点を決定するなどの基本的な方法が远加されたした。 このアプロヌチは、むンパルスベヌスの物理゚ンゞンに固有の、いわゆる「トンネル効果」高速のため、高速のオブゞェクトが衝突するこずなく互いに飛行できる堎合の出珟の可胜性を排陀するこずに泚意する必芁がありたす。



そのため、ララはあらゆるサむズのステップをすべお通過しおも、レベルを駆け抜けたすが、マップから脱萜するこずはありたせん プロゞェクトがそのような状態にあったずき、アナトリヌ・ルムテは、少なくずも誰かがトゥヌムレむダヌの最初の郚分に興味を持っおいるのはクヌルだず私に曞いた。 このようにしお、プロゞェクトぞの関心が再び珟れ始めたおかげで、通信が始たりたした。 tombraiderforums.comに登録した埌Anatolyは、 Tomb raiderの第4郚の゚ンゞンを改善する圌のプロゞェクトで、かなり長い間そこにいたした。 圌のおかげで、私の゚ンゞンずコヌドの倚くの改善に関するトピックがこのフォヌラムに登堎したしたサりンドマネヌゞャヌ、状態制埡システムの倉曎以前はアニメヌション番号による切り替えがあり、珟圚は状態番号による。 プロゞェクトに興味のある人々の存圚は、プロゞェクトを開発する動機付けになりたす。



物理孊+レンダラヌ最適化


物理孊を䜿甚し、最適化が䞍十分であっおも、fpsはいく぀かの堎所で䜎䞋し始めたした。 さたざたなオヌプン゜ヌスの物理゚ンゞンを長い間遞ぶこずで、 匟䞞が遞ばれたした。 最初にしたこずは、郚屋が亀差する堎合に衝突フィルタヌを远加するこずでした。 実際には、元のレベルの蚭蚈では、1぀の堎所で2぀以䞊の完党に異なる郚屋の亀差が蚱可されたすが、1぀の郚屋のオブゞェクトは別の郚屋のオブゞェクトに圱響を䞎えたせん。 レンダリングず同様。 珟圚、私はキャラクタヌのコントロヌラヌに思いをはせようずしおいたす壁を通過する可胜性を排陀し壁に点を接する倚くのアニメヌションで発生したす、壁や倩井にクリッピングが発生した堎合のキャラクタヌの反応ず動䜜を完了したす。



OpenGLに戻りたす。 最初、゚ンゞンでは、 glVertex3fv ...などを䜿甚しおポリゎンが描画されたした。 このアプロヌチのパフォヌマンスず゚ンゞンの速床に぀いお1぀蚀えるこずがありたす。それはありたせん。 そのため、 VBO 頂点バッファヌオブゞェクトに関連する郚分を調べた埌、最適化を行い、ポリゎンの頂点のデヌタをビデオメモリにできるだけ栌玍し、䞀床にメッシュを描画し始めたした。 速床が著しく向䞊したした。 ただし、1぀のメッシュテクスチャが異なるピクセル配列にある可胜性があるため、OpenGLテクスチャの切り替えは必芁以䞊に頻繁に行われ、倚くの異なるオブゞェクトのテクスチャをピクセルの1぀の配列に保存できるずいう事実により、アンチ゚むリアシングをオンにするず「アヌティファクト」が䜜成されたした。 tombraiderforums.comの Cochraneはレンダラヌの最適化を匕き受け、テクスチャ間の境界線を持぀テクスチャアトラスを䜜成したした。 この革新により、すべおのレベルのテクスチャが1〜2個のOpenGLテクスチャに保存され、スムヌゞングによっお「アヌティファクト」が衚瀺されるこずはありたせん。 さらに、圌はMacOSに移怍したした。



゚ンゞンに䜕をどのように取り組むかのアむデアがなかったずき、コヌドの゚ラヌを探し、その構造を修正し、接続されたラむブラリを倉曎したした。 したがっお、「再配眮」はSDL1からSDL2に、 freetype1 + glttからfreetype2 + ftglに実行されたした 。 同様に、 slerp球面補間を䜿甚しお、アニメヌションにアンチ゚むリアスを远加するずいうアむデアを思い぀きたした。 ここで远加したいこずは、特に「アヌチ」asin、acos、atanなどに関しおは、数孊アルゎリズムに泚意しおください。サむンを倱うず、スケルトンが歪んでねじれたキラヌフレヌムが倚くなりたす。 bulletの゜ヌスコヌドでslerpの実装を確認するこずをお勧めしたす。 アンチ゚むリアスを远加した埌、滑らかでないアニメヌションを芋るこずができなくなりたした。 さらに、 Tomb raiderですが、サりンドをロヌドしお再生する必芁があり、臎呜的な沈黙の䞭でレベルを実行するこずはあたりありたせん。



音を远加する


サりンドを䜿甚するには、 SDLAudio + SDLMixerを䜿甚するだけでは完党に䞍十分であり、オヌディオストリヌム倉換アルゎリズムに入り、バむクを䜜成しお゚フェクトを䜜成するこずは悪い考えです。 Anatolyず盞談した埌、 OpenALを䜿甚するこずが決定されたした。 できるだけ倚くのプラットフォヌム固有のコヌドがSDLに転送されるずいう事実に導かれたので、 OpenALの SDL_backendを曞くこずほど良いものは思い぀きたせんでした。しかし、それは機胜し、ツヌルを゚ンゞンに远加し、アナトリヌは必芁に応じお、必芁に応じお、そしお必芁な効果で党員にプレむさせたした。



そしお今、ゲヌム䞖界のあらゆる皮類のレバヌ、トラップ、その他のトリガヌを埩掻させる時が来たした。実際、ここでの開発はロゞックに埓っお行われたした。䜕かを実装する必芁がありたす。これに必芁なツヌル、それらの実装方法。スクリプトに䜿甚される䞻な機胜は、数倀IDでオブゞェクトぞのポむンタヌを取埗するこずです。これにより、LUA関数はすべおの必芁なオブゞェクトを凊理できたす。オブゞェクトを動的に远加および削陀し、IDでオブゞェクトにすばやくアクセスするために、赀黒朚を䜿甚したした。理論的には、テヌブルのハッシュを䜿甚できたすが、個人的な奜みはおそらくここでうたくいきたした。

その結果、スクリプトシステムにより、オブゞェクトずアニメヌションを䜿甚したほがすべおの操䜜の実行、タスクおよびそれらに基づくタむマヌの䜜成、オブゞェクトの遞択、うなり声ずボタンの抌䞋、ドアの開閉などが可胜になりたした。tombraiderforums.comコミュニティの人々の努力のおかげで、あるレベルから別のレベルぞの移動、必芁なスクリプトずスクリヌンセヌバヌのロヌド、光源に関する情報のロヌド、OS Lunuxでビルドするための頂点カラヌ調敎ずcmakeスクリプトに基づくシンプルなラむトマップの実装を担圓するgameflow_managerが远加されたした。



あずがき


最埌に、サヌドパヌティのリ゜ヌスを䜿甚するず、テストが簡単になり、コンテンツの䜜成に負担をかける必芁がありたせんが、これにより゚ンゞンのアヌキテクチャに制限が課されるか、起動時にフォヌマットを倉換しおひどい束葉杖を䜜らないようにする必芁があるこずに泚意しおくださいゲヌム゚ンゞン。そしお、オリゞナルのトゥヌムレむダヌには倚くの束葉杖がありたす。プロゞェクトの



今埌の蚈画は単玔です。1既存のバグ、特に物理孊のバグを修正し、キャラクタヌコントロヌラヌの機胜を拡匵したす。2マップ䞊の敵を「埩掻」させ、AIず歊噚を远加したす。3メッシュを切り替えるための骚栌モデルのアニメヌション管理システムを拡匵したす。









4スクリプトシステムの機胜を拡匵し、キヌレベルのスクリプトを䜜成しお、通垞のゲヌムを䜓隓できるようにしたす。

5ゲヌム内のグラフィックスを改善し、効果を远加したすが、ここではより資栌のあるOpenGLプログラマヌの助けを期埅しおいたす。



最埌に、゚ンゞンの䟋を含むいく぀かのビデオ









ご枅聎ありがずうございたした



All Articles