干货!教教大家如何自己制作作品!



  • 首先,我终于在忙完其它任务的情况下,抽出时间,写一篇帖子,教教大家如何制作自己的作品了。过去的一周里,我非常期待有一天自己能够写一篇这样的技术文,教教大家如何制作自己的作品。现在终于抽了个周六的晚上,把这个时间赶紧利用起来!

    首先,我要确定一下大家使用的操作系统是什么。这里推荐大家使用Windows XP或者以上的操作系统,Linux的话可能不行了,Mac电脑的话,可以安装Windows操作系统或者安装Windows虚拟机来解决。

    然后呢,需要安装的软件:
    1、MikuMikuDance,下载地址在这里 (提取码:0bc6)。我现在使用的是8.04。在安装这款软件的时候,需要检测一下是否安装了显卡驱动,因为这款软件依赖DirectX 9.0c以上。一般来说是没有问题的。

    这里呢,我们对作品的定义是集合模型、动作、语音以及人工智能的智能体。所以呢,大家可以将自己喜欢的模型、动作、语音以及人工智能按照自己的意愿结合起来,然后打包成作品,这样就能够在我这款软件中使用了!

    那么大家就有疑问了,我们上哪儿去得到模型呢?其实11区里有一个非常强大的资源集合网站,叫做bowlroll.net。大家进入这个网站,注册用户,然后就可以在资源上传者允许的情况下下载模型了!(看不懂日文?全程开谷歌翻译吧)。

    好了,当模型下载完毕后,大家会得到一个安装包,里面有很多很多纹理图片还有一个pmd文件(你的那个是pmx文件?抱歉,我们目前只做pmd,以后会逐渐扩展到pmx的),嗯,那你下载对了!

    有了模型,那么动作如何来呢?对了,我们使用刚刚下载的软件MikuMikuDance编辑动作了!MikuMikuDance上手还是很快的,不过要精通还是有点难的。下面是我使用MikuMikuDance来编辑角色“徵羽摩可”动作的截图:
    0_1448715908565_合适的机会.png
    按照网络上的一些知名的教程,就很方便地学会制作动作啦!

    接下来就是语音了。作品对语音部分没有太多的要求,只要支持wav或mp3格式的可以了。大家可以自己录音来为角色配音,也可以使用Vocaloid这样的声音合成软件来合成声音。

    最后介绍的就是人工智能了。人工智能这里使用的就是我们说的qml文件来实现。qml是一种脚本文件,主要用在Qt程序中。而我们这款应用呢,支持动态载入qml文件,也就是说,你的脚本挂在网上、Qt资源系统或者是本地,我们都可以顺利地将其载入。作品呢,将qml文件连同模型、动作、人工智能都一同打包到了zip文件中。也就是说,引擎呢,在读取的时候,对zip解压,并且读取其中的qml文件作为人工智能。目前呢,一个作品暂时只能支持一个qml格式的人工智能。

    人工智能的部分,包括了对场景的设置,以及角色的智能部分。智能的部分使用的算法是有限状态机,这种算法是目前AVG游戏常用的流程算法。其实呢,大家完全可以把这款软件当作一个开放平台的AVG软件,大家的作品就是角色,可以设置人工智能,将角色变得非常智能,TA可以在与玩家互动的同时,给玩家带来无限惊喜。

    那么,我们该如何编写人工智能文件呢?目前可能比较方便的编辑软件就是Qt Creator了。Qt Creator对QML的语法支持非常完善。然后大家就是分析文件的结构了。这里我拿一个初音未来-夕立的测试人工智能文件讲解:

    // DesignedConf.qml
    import QtQml 2.2
    import QtQml.StateMachine 1.0
    import QtQuick.Window 2.0
    import QtDream.AI.StateMachine 0.2
    import QtDream.Render 1.0
    import QtDream.Render 2.0
    import QtMultimedia 5.5
    
    QtObject
    {
        property MMDScene scene: MMDScene
        {
            color: "papayawhip"// 场景的背景颜色,可以为合法的WEB字符串或者是#开头的16进制数字
            light// MMDScene的只读成员(但是它的成员是可以写的),表示光照
            {
                enabled: true// 是否启用
                type: MMDLight.MMDStyleLight// 这里可选的有:NoLight(无光源)、SimpleLight(简单光源(聚光灯))、MMDStyleLight(MMD风格的光源(方向光源))
                lookAt: camera.lookAt// 看的位置点(建议不要修改)
                up: camera.up// 向上的向量(建议不要修改)
    
                direction: Qt.vector3d( -0.5, -1, -0.5 )// 光源方向
                ambient: Qt.rgba( 1, 1, 1 )// 环境光
                diffuse: Qt.rgba( 0.5, 0.5, 0.5 )// 漫反射
                specular: Qt.rgba( 0.6, 0.6, 0.6 )// 镜面反射
            }
    
            shadow// MMDScene的只读成员(但是它的成员是可以写的),表示阴影
            {
                enabled: false// 是否启用
                textureSize: Qt.size( 1024, 1024 )// 阴影映射(shadow map)的纹理大小(不要设置过大)
                type: MMDShadow.PCFShadow// 这里可选的有:NoShadow(无阴影)、SimpleShadow(简单阴影)、SimpleShadowDynamicBias(动态偏移的简单阴影)、PCFShadow(基于百分比平摊的阴影)
            }
    
            physics// MMDScene的只读成员(但是它的成员是可以写的),表示物理
            {
                enabled: false// 是否启用
                gravity: Qt.vector3d( 0, -9.81, 0 )// 重力向量
                drawType: MMDPhysics.DrawNone// 这里可选的有:DrawNone(不绘制)、DrawRigidBody(绘制刚体)、DrawJoint(绘制关节)、DrawAll(绘制全部)
            }
    
            option// MMDScene的只读成员(但是它的成员是可以写的),表示其它选项
            {
                boneVisible: false// 是否显示骨骼
            }
    
            Axis// 绘制坐标轴
            {
                length: 50// 长度
            }
    
            MMDModel// MMD的模型
            {
                id: hatsuneMiku
                objectName: "初音未来"// 这个会显示在面板上
                pmdSource: "../model/Miku_Hatsune_Ver2.pmd"// PMD格式的路径(写相对本文件的路径)
                edgeWidth: 0.03// 描边的宽度(默认0.03)
                property string word
    
                transforms: [ Translate { value: Qt.vector3d( -4, 0, 0 ) } ]// 位置的变换
            }
    
            MMDModel
            {
                id: yuuDachi
                objectName: "夕立"
                pmdSource: "../model/Yuu_ver1.10.pmd"
                edgeWidth: 0.03
                property string word
    
                transforms: [ Translate { value: Qt.vector3d( 4, 0, 0 ) } ]
            }
    
            camera: MouseCamera
            {
                objectName: "mouseCamera"
                position: Qt.vector3d( 0, 10, 35 )
                lookAt: Qt.vector3d( 0, 10, 0 )
                up: Qt.vector3d( 0, 1, 0 )
                fieldOfView: 45
                aspectRatio: 270 / 480
                nearPlane: 1
                farPlane: 1000
            }
        }
    
        property Audio audio: Audio { }
        property StateMachine machine: StateMachine
        {
            id: machine
            running: true
            initialState: s1
    
            StateSettings// 这里最好不要修改
            {
                id: settings
                outputWord: modelWord( hatsuneMiku ) + modelWord( yuuDachi )
            }
    
            Condition// 状态的转换
            {
                id: c2
                objectName: "c2"
                when: inputWord.indexOf( "我喜欢你" ) != -1// 触发条件
                targetState: s2// 转换对象
            }
    
            Condition
            {
                id: c3
                objectName: "c3"
                when: inputWord.indexOf( "我喜欢你" ) == -1 &&
                      inputWord.indexOf( "讨厌" ) == -1
                targetState: s3
            }
    
            Condition
            {
                id: c4
                objectName: "c4"
                when: inputWord.indexOf( "我喜欢你" ) == -1 &&
                      inputWord.indexOf( "讨厌" ) != -1
                targetState: s4
            }
    
            AIState// 状态
            {
                id: s1
                objectName: "AI:s1"
                conditions: [ c2, c3, c4 ]// 该状态可能拥有哪些转换
                onEntered:// 当进入该状态的时候作出哪些响应
                {
    
                    playAudio( audio, "../voice/succeed.mp3" );
                    playMotion( hatsuneMiku, "../motion/test_1.vmd" );
                    playMotion( yuuDachi, "../motion/test_1.vmd" );
                    hatsuneMiku.word = "你好,欢迎来到人工智能测试平台。";
                    yuuDachi.word = "你好,欢迎来到人工智能测试平台。";
                }
            }
    
            AIState
            {
                id: s2
                objectName: "AI:s2"
                conditions: [ c2, c3, c4 ]
                onEntered:
                {
                    playAudio( audio, "../voice/succeed.mp3" );
                    playMotion( hatsuneMiku, "../motion/test_1.vmd" );
                    playMotion( yuuDachi, "../motion/test_1.vmd" );
                    hatsuneMiku.word = "我也喜欢你。";
                    yuuDachi.word = "我也喜欢你。";
                }
            }
    
            AIState
            {
                id: s3
                objectName: "AI:s3"
                conditions: [ c2, c3, c4 ]
                onEntered:
                {
                    playAudio( audio, "../voice/failed.wav" );
                    playMotion( hatsuneMiku, "../motion/test_2.vmd" );
                    playMotion( yuuDachi, "../motion/test_2.vmd" );
                    hatsuneMiku.word = "我刚来到这个世界,还不太懂人类的语言,能够教教我吗?";
                    yuuDachi.word = "我刚来到这个世界,还不太懂人类的语言,能够教教我吗?";
                }
            }
    
            AIState
            {
                id: s4
                objectName: "AI:s4"
                conditions: [ c2, c3, c4 ]
                onEntered:
                {
                    playAudio( audio, "../voice/failed.wav" );
                    playMotion( hatsuneMiku, "../motion/test_2.vmd" );
                    playMotion( yuuDachi, "../motion/test_2.vmd" );
                    hatsuneMiku.word = "你好像很讨厌我。";
                    yuuDachi.word = "你好像很讨厌我。";
                }
            }
        }
        property alias inputWord: settings.inputWord
        property alias outputWord: settings.outputWord
    
        ///////////////////////////////////////////////////////////////////////////
        //
        //  一些辅助的函数
        function playAudio( audio, source, loopCount )
        {
            if ( !loopCount ) loopCount = 1;
    
            audio.stop( );
            audio.source = source;
            audio.loops = loopCount;
            audio.play( );
        }
    
        function playMotion( model, vmdSource, loopCount )
        {
            if ( !loopCount ) loopCount = 1;
    
            model.stopAnimation( );
            var modelStatusChanged = function ( )
            {
                if ( model.status === MMDModel.Ready )
                {
                    model.playAnimation( 30, loopCount );
                    model.statusChanged.disconnect( modelStatusChanged );
                }
            }
            model.statusChanged.connect( modelStatusChanged );
            model.forceLoadVMD( Qt.resolvedUrl( vmdSource ) );
        }
    
        function modelWord( model )
        {
            if ( model.word )
            {
                return "<h2>" + model.objectName + "</h2>" + model.word;
            }
            else return "";
        }
    }
    

    上面的例子我尽量将所有的都作了注释,而且,如果大家对于有限状态机的人工智能还是不太理解的话,我在CSDN上也写了一篇博客,大家可以参考一下:这里

    最后不要忘了!还有一个很重要的文件,它就是product.json。这个文件是json文件,它规定了引擎该如何读取这些文件,以及一些模型的信息。这会显示在模型的信息中。一个典型的json文件的内容如下:

    {
        "AI": [
            "AI/DesignedConf.qml"
        ],
        "comment": "这是一个最初的测试作品集合哦。",
        "model": [
            "model/Miku_Hatsune_Ver2.pmd",
            "model/Yuu_ver1.10.pmd"
        ],
        "motion": [
            "motion/test_1.vmd",
            "motion/test_2.vmd"
        ],
        "name": "最初的测试",
        "version": 100,
        "voice": [
            "voice/failed.wav",
            "voice/succeed.mp3"
        ]
    }
    

    好了,当这些大家都准备好了的话,就可以组装了!大家可以右键,然后选择“发送->压缩的文件夹”,这样稍微一压缩,一个作品就做好啦!
    0_1448718652519_打包.png

    有了作品又该如何使用呢?在我们软件的作品中心中,可以很方便地下载到需要的作品,如果大家先暂时在本地测试一下,那么右上角也有安装本地的作品这一功能。这样大家可以很方便地对自己的作品进行测试了。如果大家想要发布,那么可以在我们论坛的“作品中心”发表自己的作品,当然了,发表的作品和我们的应用共通,这样的话,可以很方便地在应用中下载到自己喜爱的作品。大家把自己的得意之作都分享一下吧!
    0_1448719046846_Screenshot_2015-11-28-21-55-53.png
    最后,我希望能够借这个教程,抛砖引玉,大家能够充分地调动创作的激情,制作出让玩家满意的作品!



  • @jiangcaiyang
    我什么时候丢了个作品了?应该是最后回复吧。。。



  • @qyvlik 这个在第三版中已经修正。



  • 小米二进不去啊,在登陆界面点击登陆,然后就闪退了



  • @药师 那可能是因为你的Android系统没有升级,我这个在Android 4.4.4 KitKat是没有问题的。支持的Android API是21。



  • @药师 我用Android API-19编译一个版本,然后单独发给你试试看。



  • @jiangcaiyang 收到,回家试试看


Log in to reply
 

走马观花

最近的回复

  • Z

    我按照楼主说的,除了windeployqt以外,还需要Qt\labs\folderlistmodel,QtQuick\Layouts,QtQuick\VirtualKeyboard,QtQuick.2,platforminputcontexts这么几个目录,另外Qt5Qml.dll, Qt5Quick.dll也要加进来,还是不行,楼主可以发个完整的文件夹给我吗,release模式的,debug模式没问题

    read more
  • 1.jpg

    简介

    USD全称是“Universal Scene Description”,它主要着力的是电影、游戏复杂制作的流程的规范化。这回我们主要来研究USD在Maya中有关渲染部分究竟是如何实现的。

    USD通过附属的子项目Hydra来实现在其工具“UsdView”以及Maya中渲染方面的实现。Hydra是一款经过多年锤炼的渲染引擎(据说自从2012年就开始研发),Hydra与Maya中有关渲染的结合得益于Maya支持第三方渲染框架通过它提出的“Viewport (1.0、2.0)”方式支持。

    初探

    我们打开USD项目,会发现它有很多子项目。其中包含了imaging和usd子项目。这里我们主要关注的是imaging项目。由单词意思可知,其主要关注的是产出图片的,也就是有关渲染的。
    ca7ce8ff-7e7d-4ec3-9246-fbee48109c7b-image.png

    有关Hydra是三个子项目。包含了hdSt、hdStream以及hdx。我们主要关注的是hdSt。因为这个项目是和OpenGL渲染密切相关的。由于OpenGL是做渲染的大家通用知识,因而它是我们关注的主要子项目。

    调试

    调试.jpg
    我们调试这部分代码,截了此图。我们在图中至少发现几个问题:

    Maya底层是用OpenSceneGraph(OSG)管理场景的。我们可以看到Maya 2018的文件夹里有很多OSG开头的动态链接库,这么说具体视口渲染的部分都是建立在OSG上的。Maya的Viewport 2.0也是建立在OSG上的。在可见的将来它们不会替换掉OSG。

    USD的Hydra和Maya结合的类叫做UsdMayaGLBatchRenderer。由名字可知,它仅工作在OpenGL下,换句话说,如果Maya使用的是DirectX11进行渲染的,那么它将会失效。
    d7063ec4-242e-40bd-bf05-5103c38fedf1-image.png

    libhd项目只是一个前端库,后端通过libhdx以及libhdSt来实现。尤其是libhdSt,它主要是和OpenGL打交道的。它十分复杂。主要基于的是OpenGL 3.3+,也就是包含了各种着色器以及高级栅格化技术,并且整合了网格细分库:OpenSubdiv。

    后续

    由于我们的研究方向是思考一个方式来让让Hydra支持过程化纹理,因此我们还需要继续对此进行研究。

    read more
  • 最近我开始在Maya工作啦。Maya是一款优秀的三维软件,可以处理布景、建模、纹理、装备、渲染等操作。而且它可以支持C++和Python的开发。文档也是非常多的(参考这里)。

    我们最近的工作呢,是想要利用Maya的资源,尤其是图片资源,来制作新的界面。由于Maya是基于Qt开发的,因此要获取图片资源,除了Maya文件夹里D:\Develop\Autodesk\Maya2018\icons文件之外,还需要从资源文件中获取到。而资源文件一般是编译成C++代码放在程序的某个位置了,所以我们一般是看不到的。我们就开始想,既然Maya能够成功地读取并且显示,我们通过Maya的插件开发,不也能够获取并且显示到需要的图标吗?由于Qt的经验,我开始研究通过写Maya插件来获取到Maya资源文件的方法。

    新打开Maya软件,点击右下角的脚本图标,我们开始输入脚本:
    ad141613-63a5-4a93-9b79-3d6ca44da782-image.png

    2、我们通过Qt for Python来从Maya中获取到图标信息。由于Maya是构建在Qt 5.6.1上的,当时还不叫Qt for Python,而是PySide2。当然用法也是差不多的。更重要的是Qt 5.6.1已经支持QML了,可以支持QML的基本绘图方法。所以我们打算结合Qt for Python和QML来实现相关的功能。其实这样一组合就和Maya没有什么关系了。剩下的都是Qt的技术。
    我们的脚本是这样的:

    from PySide2.QtQuick import QQuickView from PySide2.QtCore import QDir, QFileInfo, QUrl def getMayaResourceFileList( nameFilter ): dir = QDir( ":/" ) return dir.entryList( nameFilter ) view = QQuickView( ) view.setResizeMode( QQuickView.SizeViewToRootObject ) mainUrl = QUrl.fromLocalFile( "C:/Users/huawei/Documents/ImageGridView.qml" ) view.setSource( mainUrl ) view.show( ) rootItem = view.rootObject( ) if rootItem != None: rootItem.setProperty( "prefix", "qrc:/" ) rootItem.setProperty( "model", getMayaResourceFileList( '*.png' ) )

    其中C:/Users/huawei/Documents/ImageGridView.qml是我本地的路径,可以改为任意的路径甚至是http路径呢。

    我们还得完成ImageGridView.qml文件内容,其实也非常简单,大概是这样的: import QtQuick 2.6 GridView { id: root width: 320 height: 480 cellWidth: 80 cellHeight: 80 delegate: Image { width: 80 height: 80 source: root.prefix + modelData Text { anchors { left: parent.left right: parent.right bottom: parent.bottom } text: modelData wrapMode: Text.Wrap } } model: 40 property string prefix }

    22ae8472-0bb3-4342-ae72-e1cb54bd87a7-image.png

    4、这些文件准备就位了!我们打开一下Maya软件,看看结果~
    f430332d-c824-47d3-8d1c-b17b3c53bc97-image.png
    它是一个可滑动的界面,每行显示4列,然后下面是文字的内容,展示了图标的名称。我们可以借此工具拿到我们感兴趣的图标的路径,然后应用到我们制作的界面上。其实,如果你觉得图片好,也可以通过QImage以及QPixmap给转存出去自己用。

    如果你觉得文章很棒,记得点赞哦。

    read more

关注我们

微博
QQ群











召唤伊斯特瓦尔