標籤:

從零開始手敲次世代遊戲引擎(MacOS特別篇 貳)

上一篇我們實現了在macOS上面的編譯。但是我們採用的並不是macOS的原生介面,因此獲得的OpenGL Context所支持的API版本也被限制到2.1版本。這顯然是不夠的。

因此,接下來我們來嘗試使用macOS的原生圖形介面 —— Cocoa,來完成OpenGL Context的創建工作。

Cocoa是基於Objective-C的一套Apple的獨有系統。Objective-C是一種比較古老的語言,是C語言的超集,但是與C++語言又非常不同。作者在寫這篇文章之前並沒有接觸過Objective-C語言,所以這兩天查閱了大量的資料來「臨時抱佛腳」。這裡需要特別感謝 @袁全偉 分享的相關資料整理。我把這些資料會同我自己查閱到的資料都列在了參考引用當中。

實際寫下來,感覺Cocoa類似Windows平台上的MFC,或者.NET的感覺,封裝還是比較徹底的。好處當然是非常簡單易用,但是同時也就意味著很多的細節被隱藏。在我早年學習MFC的時候,想要實現同時期Office所提供的一些很酷的控制項效果,就發現非常的不容易。後來學習了Win32 API,發現就能很方便的實現了。這兩天對Cocoa的突擊學習又讓我感覺似乎一下子回到了20多年前,找到了那種有力無處使的感覺。

但是到目前為止我還沒有能夠找到在macOS上比Cocoa更為底層的API介面。所以我們就將就著用吧。

Cocoa裡面至少包含了兩個Kit:AppKit和UIKit。AppKit提供了應用程序的框架結構,而UIKit提供了UI組件。最為方便地了解Cocoa的方法是使用XCode生成一個基於Cocoa的應用程序項目,這個過程與使用Visual Studio的嚮導創建項目非常類似,僅僅需要點擊幾次滑鼠,輸入應用程序名什麼的,就能夠生成一個基本的應用程序(含窗體)的基本結構。

Xcode啟動頁面。選擇Create a new Xcode project

應用程序嚮導界面。先選擇macOS,然後選擇Cocoa App

輸入產品名Hello Cocoa,Language選Objective-C,取消所有勾選了的複選框,保持項目為最簡狀態

這樣一個基本的Cocoa應用(含窗體)項目就生成好了。目錄結構大致如下:

.n├── Hello Cocoan│ ├── AppDelegate.hn│ ├── AppDelegate.mn│ ├── Assets.xcassetsn│ │ └── AppIcon.appiconsetn│ │ └── Contents.jsonn│ ├── Base.lprojn│ │ └── MainMenu.xibn│ ├── Hello_Cocoa.entitlementsn│ ├── Info.plistn│ └── main.mn└── Hello Cocoa.xcodeprojn ├── project.pbxprojn └── project.xcworkspacen └── contents.xcworkspacedatan

程序的主入口是 main.m,AppDelegate.h和AppDelegate.m定義了應用程序代理類,也就是可以對標準的Cocoa Application進行擴展的地方。Assets.xcassets當中是應用程序的一些資源文件,比如圖標什麼的。Base.lproj當中是被稱為InterfaceBuilder工具的界面定義文件,就是所謂的WYSIWYG(What You See Is What You Get,所見即所得)的圖形界面編輯器文件。Hello_Cocoa.entitlements和Info.plist,這兩個都是XML格式的類似manifest的文件,用來向操作系統或者是APN等提供程序相關的meta data的。而Hello Cocoa.xcodeproj目錄當中的則是Xcode的項目文件。

我們可以繼續在Xcode當中編譯這個項目並執行。如果採用命令行,那麼編譯的命令如下(假設我們在項目根目錄):

chenwenlideMBP:Hello Cocoa chenwenli$ xcodebuild -project Hello Cocoa.xcodeproj/ buildn

然後執行

chenwenlideMBP:Hello Cocoa chenwenli$ build/Release/Hello Cocoa.app/Contents/MacOS/Hello Cocoan

就可以看到我們的第一個Cocoa窗口了:

然而,我們需要如何將它和我們之前的代碼結合到一起呢?有沒有可能將Objective-C和C++混合進行編程呢?

答案是可以的。Objective-C在經過編譯之後,生成的二進位文件是與C語言有著同樣的二進位介面的。也就是有二進位兼容性(Swift也是一樣)。只不過,Object-C的面向對象模型與C++不同,不能簡單地將兩者等同起來(也就是運行時間庫是不一樣的)。在macOS全面採用clang作為編譯工具之後,由於llvm中間層的存在,Objective-C與C++的互換變得更為方便,甚至出現了Objective-C++這種可以將兩者同時寫在一個文件當中的「編程語言」(新版本gcc也支持)。不過這裡要注意,實際上Objective-C++並不是一種真正的新的語言,書寫在源代碼當中的Objective-C代碼和C++代碼其實是相對獨立的被編譯處理之後又鏈接在一起的。

好了,那麼接下來讓我們基於Xcode所生成的模版,按照我們自己所寫引擎的架構和邏輯,編寫Cocoa版本的Application和OpenGL Context創建代碼。(將Objective-C代碼嵌入到我們的C++代碼當中)

首先我們將XcbApplication.{hpp,cpp}分別拷貝並改名為CocoaApplication.{h,mm}。mm是Objective-C++源代碼的後綴。然後編輯如下:

CocoaApplication.h

#include "BaseApplication.hpp"n#include <Cocoa/Cocoa.h>nnnamespace My {n class CocoaApplication : public BaseApplicationn {n public:n CocoaApplication(GfxConfiguration& config)n : BaseApplication(config) {};nn virtual int Initialize();n virtual void Finalize();n // One cycle of the main loopn virtual void Tick();nn protected:n NSWindow* m_pWindow;n };n}n

CocoaApplication.mm

#include <string.h>n#include "CocoaApplication.hpp"n#include "MemoryManager.hpp"n#include "GraphicsManager.hpp"n#include "SceneManager.hpp"n#include "AssetLoader.hpp"nn#import <AppDelegate.h>n#import <WindowDelegate.h>nnusing namespace My;nnnamespace My {n GfxConfiguration config(8, 8, 8, 8, 24, 8, 0, 960, 540, "Game Engine From Scratch (MacOS Cocoa)");n IApplication* g_pApp = static_cast<IApplication*>(new CocoaApplication(config));n GraphicsManager* g_pGraphicsManager = static_cast<GraphicsManager*>(new GraphicsManager);n MemoryManager* g_pMemoryManager = static_cast<MemoryManager*>(new MemoryManager);n AssetLoader* g_pAssetLoader = static_cast<AssetLoader*>(new AssetLoader);n SceneManager* g_pSceneManager = static_cast<SceneManager*>(new SceneManager);n}nnint CocoaApplication::Initialize()n{n int result = 0;nn [NSApplication sharedApplication];nn // Menun NSString* appName = [NSString stringWithFormat:@"%s", m_Config.appName];n id menubar = [[NSMenu alloc] initWithTitle:appName];n id appMenuItem = [NSMenuItem new];n [menubar addItem: appMenuItem];n [NSApp setMainMenu:menubar];nn id appMenu = [NSMenu new];n id quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"n action:@selector(terminate:)n keyEquivalent:@"q"];n [appMenu addItem:quitMenuItem];n [appMenuItem setSubmenu:appMenu];nn id appDelegate = [AppDelegate new];n [NSApp setDelegate: appDelegate];n [NSApp activateIgnoringOtherApps:YES];n [NSApp finishLaunching];nn NSInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |n NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;nn m_pWindow = [[NSWindow alloc] initWithContentRect:CGRectMake(0, 0, m_Config.screenWidth, m_Config.screenHeight) styleMask:style backing:NSBackingStoreBuffered defer:NO];n [m_pWindow setTitle:appName];n [m_pWindow makeKeyAndOrderFront:nil];n id winDelegate = [WindowDelegate new];n [m_pWindow setDelegate:winDelegate];nn return result;n}nnvoid CocoaApplication::Finalize()n{n [m_pWindow release];n}nnvoid CocoaApplication::Tick()n{n NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAnyn untilDate:niln inMode:NSDefaultRunLoopModen dequeue:YES];nn switch([(NSEvent *)event type])n {n case NSEventTypeKeyDown:n NSLog(@"Key Down Event Received!");n break;n default:n break;n }n [NSApp sendEvent:event];n [NSApp updateWindows];n [event release];n}n

這裡需要特別說明的就是我們去掉了[NSApp run],取而代之在Tick()當中使用了我們自己的EventLoop。這是為了保證我們的主循環在我們自己所寫的main函數(最終會在驅動模塊)當中。

然後在Platform/Darwin/CMakeLists.txt當中添加如下編譯規則:

# MyGameEngineCocoanadd_executable(MyGameEngineCocoa MACOSX_BUNDLEn CocoaApplication.mm n AppDelegate.mn WindowDelegate.mn )nfind_library(COCOA_LIBRARY Cocoa required)ntarget_link_libraries(MyGameEngineCocoa n Common n ${OPENGEX_LIBRARY} n ${OPENDDL_LIBRARY} n ${XG_LIBRARY} n ${COCOA_LIBRARY} n )n__add_xg_platform_dependencies(MyGameEngineCocoa)n

執行build.sh之後,就會在./build/Platform/Darwin/MyGameEngineCocoa.app/Contents/MacOS/MyGameEngineCocoa當中生成我們的可執行文件。執行它就可以看到我們的窗體了:

看上去不錯哦。然後讓我們加入對於OpenGL的初始化代碼。首先根據參考引用*4,創建兩個新文件,GLView.{h,mm},從NSView派生出我們自己的View類:

GLView.h

#import <Cocoa/Cocoa.h>nn@interface GLView : NSViewn{n @privaten NSOpenGLContext* _openGLContext;n NSOpenGLPixelFormat* _pixelFormat;n}nn@endn

GLView.mm

#import "GLView.h"n#import <OpenGL/gl.h>nn@implementation GLViewnn- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)formatn{n self = [super initWithFrame:frameRect];n return self;n}nnnn- (void)drawRect:(NSRect)dirtyRect {n [super drawRect:dirtyRect];nn [_openGLContext makeCurrentContext];nn glClearColor(1,0,1,1);n glClear(GL_COLOR_BUFFER_BIT);nn [_openGLContext flushBuffer]; n}nnnn- (instancetype)initWithFrame:(NSRect)frameRectn{n self = [super initWithFrame:frameRect];nn NSOpenGLPixelFormatAttribute attrs[] = {n NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,n NSOpenGLPFAColorSize,32,n NSOpenGLPFADepthSize,16,n NSOpenGLPFADoubleBuffer,n NSOpenGLPFAAccelerated,n 0n };nn _pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];n if(_pixelFormat == nil)n {n NSLog(@"No valid matching OpenGL Pixel Format found");n return self;n }nn _openGLContext = [[NSOpenGLContext alloc] initWithFormat:_pixelFormat shareContext:nil];nnn [[NSNotificationCenter defaultCenter] addObserver:selfn selector:@selector(_surfaceNeedsUpdate:)n name:NSViewGlobalFrameDidChangeNotificationn object:self];nn [_openGLContext makeCurrentContext];nn return self;n}nnn- (void)lockFocusn{n [super lockFocus];n if([_openGLContext view]!= self)n {n [_openGLContext setView:self];n }n [_openGLContext makeCurrentContext];nn}nn- (void)updaten{n [_openGLContext update];n}nn- (void) _surfaceNeedsUpdate:(NSNotification*) notificationn{n [self update];nn}nnnnn@endn

然後再添加兩個文件,CocoaOpenGLApplication.{h,mm},從CocoaApplication派生出帶有OpenGL Context(GLView)的應用類型,在CocoaApplication的初始化之後,將GLView的實例替換到窗口客戶區:

int CocoaOpenGLApplication::Initialize()n{n int result = 0;nn result = CocoaApplication::Initialize();nn GLView* view = [[GLView alloc] initWithFrame:CGRectMake(0, 0, m_Config.screenWidth, m_Config.screenHeight)];nn [m_pWindow setContentView:view];nn return result;n}n

最後是修改CMakeLists.txt。已經很長了,不贅述了。最後運行效果如題圖。完成的代碼在mac2分支當中。

參考引用

  1. Getting Started - OpenGL Wiki
  2. Programming OpenGL on macOS
  3. Creating a Cocoa application without NIB files
  4. OpenGL for macOS
  5. Drawing to a Window or View
  6. MACOSX_BUNDLE - CMake 3.0.2 Documentation
  7. AppKit | Apple Developer Documentation
  8. Demystifying NSApplication by recreating it
  9. Minimalist Cocoa programming
  10. Objective-C - Wikipedia
  11. Mixing Objective-C, C++ and Objective-C++: an Updated Summary

推薦閱讀:

遊戲不再是增量市場,它已經充滿了不確定性
國外大學中,有哪些大學開設了網路遊戲開發,有公開課么?
木七七陸家賢一年的思考與總結:戰略、新品以及管理?
【內部分享】我眼中的CocosCreator by 陳棟
初學者教學——從零開始學做射擊遊戲(二)

TAG:游戏开发 |