17 2012

AVG式2D战棋游戏:传承的思念(暂定)开坑

这次是真的要开始写系统化的游戏了。

定位是2D的SLG类游戏,并且融合AVG。类似于我之前加入的幻爵工作室的《遗忘的战场》。这次我也是加入了一个民间的游戏工作室,里面有像素师、音效师、脚本策划和程序猿了-。 -

《传承的思念》,这个名字是暂定的。

由于别的程序猿们的时间关系,我就很蛋疼地成了主程。现在正在开坑中,为了写这个游戏而开始写引擎构架。

构架的思想是这样的:

渲染引擎将HGE整合进去,然后涵盖了很多功能,比如说重新封装了精灵类、GUI类、资源内存管理器、资源载入管理器、场景管理器、渲染精灵池、文件包管理器等等等等。这一切将会合成一个新的聚合性引擎:XAEngine。

当前只是完成了构架的一小部分。而为了测试这一小部分的构架我也一直在写demo来看看功能的完整性以及准确性。很遗憾,貌似最后一次Commit到SVN的时候,这个版本里加了不知道是鼠标管理器还是渲染精灵池,运行时内存一直在增长,弄得我蛋疼死了。

先放出demo预览一下吧。(注:demo只是看看渲染效果和GUI效果,并没有任何游戏逻辑。而且名字本身也是我自己乱取的。素材来源均来自于网络,包括二小姐的立绘(这是黄昏的大作)、沙耶的BGM(这是从原作里提取出来的)以及二小姐的BGM等等。还有文本框是从遗忘的战场里拿来的,初始画面的衬底是从迷宫2里面提取出来的)

点我下载

Title-ScreenAVG-Screen


18 2011

HGE做格斗游戏的热点图片碰撞检测法

碰撞检测始终是做2D游戏中的一个热点话题,我本人并没有做过这类游戏,所以一切只是理论而已,不过正打算做这么个小游戏练练手。

前几天在HGE的群里看到有人突然问到如何判断鼠标有没有点到人(点到纹理的透明区域不算),从而引申出了碰撞检测问题。

他的问题相对好实现,只要算出纹理所按的点是不是透明即可。

接下来我得做下碰撞检测的笔记:

碰撞检测最常用一个方法就是关节设置(当然我并没有做过),关节设置的话因为只是判断多边形的重叠状况,算法的复杂度低、效率高,虽然做工有点粗,但总体效果还是性价比比较高的一种方法。当然,这样的方法需要对每一帧的纹理都设置一个关节,对于人工的代价就稍微大了一些了,并且还要写个关节编辑器啊神马的,于是乎代码量又增加了。我这次是和同寝室木有一点基础的童鞋一起练手的,所以并没有打算引进这个方法。

于是我就用了另一种稍微“非主流”一些的方法了——逐像素判断。

但是逐像素判断还是有问题的——如果你的一个“效果”因为“温度过高”而不需要显示,直接隐藏,但又算伤害,这时纹理的逐像素就失去了意义。于是又有了个“臃肿”的办法,为需要“额外附加像素”的纹理另做一张图片,这张图片上有两种区域——热点区和非热点区。我们把需要“当做空气”的那些区域一律用某一种极其不常用的颜色覆盖,如ff00ff这种变态的粉色,然后其它区域的颜色就随你怎么搞了。我们载入的时候两张纹理一起载入,显示的时候显示正常的纹理,而在碰撞检测的时候用“热点图片”来进行逐像素检测。

与上面的关节设置法比较的话,人工的工作量我个人认为是大大地减少了,至于对于机器的执行能力来说,把时间复杂度提到了O(mn),平方级的复杂度了,即纹理相交区域的宽和高。

我们来看一下这种碰撞检测的大体流程吧:

1、获得两个精灵的矩形,并得到相交矩形。若无相交则直接返回false。

2、根据相交矩形,我们可以得到精灵1、2的纹理中需要检测的初始坐标。

3、将精灵1、精灵2的热点图片的相交区域的那一部分像素拷贝出来备用。(因为有可能两个纹理句柄是一样的,不好同时lock)

4、开始对于拷贝出来的像素信息逐一判断对应像素点是否都“不是空气”,若都“不是空气”则可以判断为碰撞。

当然以上的流程我们还可以优化一下,省去拷贝的那一段时间。我们可以直接hge->Texture_Lock()来进行得到两个纹理的像素信息的首指针,如果两个纹理其实只是一个纹理的话,则只需hge->Texture_Lock()一次,而另一个指针也只想hge->Texture_Lock即可,然后直接开始判断。

下面献上我这个函数的实现以及测试代码和素材:

/**
 * @brief   Test the collision by the "hot" texture
 * @author  XadillaX
 * @email   admin@xcoder.in
 * @date    2011/10/18
 * @http://xcoder.in
 *
 * @param spr1 The first sprite to test the collision
 * @param x1 "x" of top-left corner of sprite 1
 * @param y1 "y" of top-left corner of sprite 1
 * @param spr2 The second sprite to test the collision
 * @param x2 "x" of top-left corner of sprite 2
 * @param y2 "y" of top-left corner of sprite 2
 * @param hot1 The "hot" texture for sprite 1. It will be the default texture of spr1 if it equal to 0
 * @param hot2 The "hot" texture for sprite 2. It will be the default texture of spr2 if it equal to 0
 * @param airColor The color which considered of "air"
 *
 * @return if they are collided, return true
 */
bool IsCollision(hgeSprite* spr1, float x1, float y1, hgeSprite* spr2, float x2, float y2, HTEXTURE hot1 = 0, HTEXTURE hot2 = 0, DWORD airColor = 0xffff00ff)
{
    /** Set the rect */
    hgeRect r1, r2;
    r1.Set(x1, y1, x1 + spr1->GetWidth(), y1 + spr1->GetHeight());
    r2.Set(x2, y2, x2 + spr2->GetWidth(), y2 + spr2->GetHeight());

    /** Test for the intersect of rectangles */
    if(r1.Intersect(&r2))
    {
        int x[] = { x1, x2, x1 + spr1->GetWidth(), x2 + spr2->GetWidth() };
        int y[] = { y1, y2, y1 + spr1->GetHeight(), y2 + spr2->GetHeight() };
        std::sort(x, x + 4);
        std::sort(y, y + 4);
        hgeRect r;

        /** Set the rectangle area where the two rectangles intersected. */
        r.Set(x[1], y[1], x[2], y[2]);

        /** The start point of sprite1 and sprite2. (From the intersected area) */
        int sx1, sy1, sx2, sy2;
        sx1 = x[1] - x1;
        sy1 = y[1] - y1;
        sx2 = x[1] - x2;
        sy2 = y[1] - y2;

        /** Get the "hotspot" of texture */
        HTEXTURE hTex1 = hot1;
        HTEXTURE hTex2 = hot2;
        if(hTex1 == 0) hTex1 = spr1->GetTexture();
        if(hTex2 == 0) hTex2 = spr2->GetTexture();

        float tx1, ty1, tw1, th1, tx2, ty2, tw2, th2;
        int w1 = hge->Texture_GetWidth(hTex1), w2 = hge->Texture_GetWidth(hTex2);
        spr1->GetTextureRect(&tx1, &ty1, &tw1, &th1);
        spr2->GetTextureRect(&tx2, &ty2, &tw2, &th2);

        DWORD* color1 = new DWORD[(x[2] - x[1]) * (y[2] - y[1])];
        DWORD* color2 = new DWORD[(x[2] - x[1]) * (y[2] - y[1])];
        DWORD* color;

        /** Copy the effectivearea of texture 1 */
        color = hge->Texture_Lock(hTex1, true);
        for(int i = 0; i < y[2] - y[1]; i++)
        {
            for(int j = 0; j < x[2] - x[1]; j++)
            {
                color1[i * (x[2] - x[1]) + j] = color[((int)ty1 + sy1) * w1 + (int)tx1 + sx1 + i * w1 + j];
            }
        }
        hge->Texture_Unlock(hTex1);

        /** Copy the effectivearea of texture 2 */
        color = hge->Texture_Lock(hTex2, true);
        for(int i = 0; i < y[2] - y[1]; i++)
        {
            for(int j = 0; j < x[2] - x[1]; j++)
            {
                color2[i * (x[2] - x[1]) + j] = color[((int)ty2 + sy2) * w2 + (int)tx2 + sx2 + i * w1 + j];
            }
        }
        hge->Texture_Unlock(hTex2);

        /** Test for the collision */
        for(int i = 0; i < y[2] - y[1]; i++)
        {
            for(int j = 0; j < x[2] - x[1]; j++)
            {
                if(color1[i * (x[2] - x[1]) + j] != airColor && color2[i * (x[2] - x[1]) + j] != airColor)
                {
                    delete []color1;
                    delete []color2;

                    return true;
                }
            }
        }

        delete []color1;
        delete []color2;
        return false;
    }
    else return false;
}
点我下载

14 2011

测测你的词汇量:我只有悲催的4000左右

这个网站是测试你的英文词汇量用的,你可以去试试:Test Your Vocab

 


13 2011

关于HGE的透明背景处理

嘛 = = 在做那个项目的动画预览器的时候,因为那引擎封装得太麻烦了,于是自己基于HGE再移植一遍,发现其中有一个SetTransparentColor函数,即设置透明色。

拿出来分享一下吧。

其实方法很简单,HTEXTURE是纹理句柄,当你用Texture_Lock这个函数锁定这个纹理的时候,它的返回值就是这个纹理在内存中的首地址。也就是说接下来的width * height个地址中就是这个纹理的每一个像素了。既然要设置透明色,只要对于每个像素判断一下与运算一下就好了。

HTEXTURE SetTransColor(HTEXTURE hTex, DWORD dwColor)
{
    /** 注:上面的dwColor代表的是RGB,不是ARGB */
    static HGE* hge = hgeCreate(HGE_VERSION);

    int size = hge->Texture_GetWidth(hTex) * hge->Texture_GetHeight(hTex);
    DWORD* dwTex = hge->Texture_Lock(hTex);
    for(int i = 0; i < size; i++)
    {
        if((dwTex[i] & 0x00FFFFFF) == dwColor)
        {
            dwTex[i] &= 0x00FFFFFF;
        }
    }
    hge->Texture_Unlock(hTex);

    return hTex;
}

嘛,这样一来,就透明了~


5 2011

Squirrel脚本笔记——嵌入C++入门

最近在看Squirrel这个轻量级脚本语言,感觉以后放到游戏里还是挺管用的,因为Lua的嵌入编程虽然强大但是感觉有些复杂,对于我这个怕麻烦的人来说,Squirrel应该够吧。首先这个脚本语言并不知名,可以说是毫无名气,所以网上的中文资源也是少之又少。那就自食其力吧。

跑到它的官方Wiki上还是有一些人的教程的,于是我就做做贡献小小翻译一下了。

有兴趣的童鞋可以点击下面来看翻译内容。这是其中一篇入门教程,名为Embedding Getting Started,作者是Alberto。

› 阅读全文


28 2011

基于ZeroMQ的一对一网络类

偶尔在云风的博客看到了ZeroMQ这个网络库,想到我自己刚好要做课程设计,于是便载下来粗粗看了一下。

因为我做的是KTV系统的点播端与播放端间的通信,所以是一对一模式,只需要Request-Reply模式就可以了。模型图如下,没有Accept过程:

 

客户端向服务端发送一个请求(REQ),然后服务端接收到请求之后给予一个答复(REP),而我的KTV系统也是这样的,播放端请求一首新歌,点播端将播放队列的队首答复回去,或者点播端请求暂停歌曲,播放端回复暂停成功与否。总之两个应用程序间互为服务端客户端。

不过是因为赶工,所以也没仔细设计,权当学习用,消息的数据体也没有加密什么的,而消息结构倒是有点仿照网狐。

用法其实很简单,只要新建一个CKTVNetwork121对象,传入的值是本机绑定的地址、服务器的地址以及接收响应函数(相当于OnReceive,这个函数为void类型,传入四个参数,分别为(int 消息分类ID, int 消息ID, char* 消息体, size_t 消息大小))。

CKTVNetwork121.h

//////////////////////////////////////////////////////////////////////////
//
// KTV一对一网络类
//
// Program by 死月(XadillaX) (admin@xcoder.in)
// Create Date: 2011/08/26
//
//////////////////////////////////////////////////////////////////////////
#ifndef CKTVNETWORK121_H
#define CKTVNETWORK121_H

#pragma once
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#include "cktverror.h"
using namespace std;

#define MAGIC_NUM                   (DWORD)(0x49444158)
typedef void                        (*ON_RECEIVE_FUNC)(int, int, char*, size_t);

struct CKTVNetworkHeader
{
    DWORD                           MagiNum;
    int                             MainID;
    int                             SubID;
    size_t                          Size;
};

struct SendData
{
    CKTVNetworkHeader*              header;
    char*                           data;
};

class CKTVNetwork121
{
friend DWORD WINAPI SendThread(LPVOID lpParam);
friend DWORD WINAPI ReceiveThread(LPVOID lpParam);

public:
    CKTVNetwork121(const char* szServer, const char* szClient, ON_RECEIVE_FUNC func = NULL);
    virtual ~CKTVNetwork121(void);

    void                            SendMsg(int MainID, int SubID, const char* pData, size_t size);

private:
    zmq::context_t                  m_CtxServer;
    zmq::socket_t*                  m_pSktServer;

    zmq::context_t                  m_CtxClient;
    zmq::socket_t*                  m_pSktClient;

    HANDLE                          m_hSendThread;
    HANDLE                          m_hReceiveThread;

    bool                            m_bConnected;
    string                          m_szConnAddr;

    queue<SendData>                 m_SendQueue;
    CRITICAL_SECTION                m_CriticalSection;
};

#endif

CKTVNetwork121.cpp

#include "CKTVNetwork121.h"

struct _receive_param
{
    zmq::socket_t* socket;
    ON_RECEIVE_FUNC func;
};

struct _send_param
{
    zmq::socket_t* socket;
    CRITICAL_SECTION* cs;
    queue* queue;
};

DWORD WINAPI SendThread(LPVOID lpParam)
{
    _send_param* sp = (_send_param*)lpParam;
    zmq::socket_t* socket = sp->socket;
    CRITICAL_SECTION* cs = sp->cs;
    queue* queue = sp->queue;
    delete sp;

    while(true)
    {
        SendData sd;
        sd.header = NULL;
        sd.data = NULL;

        /** 从队列中获取 */
        ::EnterCriticalSection(cs);
        if(!queue->empty())
        {
            sd = queue->front();
            queue->pop();
        }
        ::LeaveCriticalSection(cs);

        /** 有消息 */
        if(sd.header != NULL && sd.data != NULL)
        {
            try{
                /** 数据 */
                char* pData = new char[sd.header->Size + sizeof(CKTVNetworkHeader)];
                memcpy(pData, sd.header, sizeof(CKTVNetworkHeader));
                memcpy(pData + sizeof(CKTVNetworkHeader), sd.data, sd.header->Size);
                zmq::message_t request(sizeof(CKTVNetworkHeader) + sd.header->Size);
                memcpy(request.data(), pData, sizeof(CKTVNetworkHeader) + sd.header->Size);

                /** 发送 */
                socket->send(request);

                /** 反馈 */
                zmq::message_t reply;
                socket->recv(&reply);

                delete []pData;
                delete []sd.data;
                delete sd.header;
            }
            catch(zmq::error_t e)
            {
                printf("[ERROR 0x%X] %s\n", e.num(), e.what());
            }
        }

        Sleep(1);
    }

    return 0;
}

DWORD WINAPI ReceiveThread(LPVOID lpParam)
{
    _receive_param* rp = (_receive_param*)lpParam;
    while(true)
    {
        /** 接收数据 */
        zmq::message_t request;
        rp->socket->recv(&request);

        /** 分析数据 */
        CKTVNetworkHeader Header;
        char* pData = (char*)request.data();
        memcpy(&Header, pData, sizeof(CKTVNetworkHeader));

        /** 验证 */
        if(MAGIC_NUM != Header.MagiNum) continue;

        /** OnReceive */
        if(NULL != rp->func)
        {
            rp->func(Header.MainID, Header.SubID, pData + sizeof(CKTVNetworkHeader), Header.Size);
        }

        /** 返还数据 */
        zmq::message_t reply(0);
        rp->socket->send(reply);

        Sleep(1);
    }
    delete rp;

    return 0;
}

CKTVNetwork121::CKTVNetwork121(const char* szServer, const char* szClient, ON_RECEIVE_FUNC func) :
    m_CtxServer(1),
    m_hReceiveThread(0),
    m_hSendThread(0),
    m_pSktClient(NULL),
    m_CtxClient(2)
{
    m_pSktServer = new zmq::socket_t(m_CtxServer, ZMQ_REP);

    try {
        m_pSktServer->bind(szServer);
    }
    catch(zmq::error_t& t)
    {
        //THROW_KTV_ERROR(t.num(), t.what());
        assert(0);
        return;
    }

    m_szConnAddr = szClient;

    /** 接受线程 */
    _receive_param* rp = new _receive_param();
    rp->socket = m_pSktServer;
    rp->func = func;

    ::InitializeCriticalSection(&m_CriticalSection);
    m_hReceiveThread = ::CreateThread(NULL, 0, ReceiveThread, rp, 0, 0);
}

CKTVNetwork121::~CKTVNetwork121(void)
{
    if(m_hReceiveThread != 0) ::TerminateThread(m_hReceiveThread, 0);
    if(m_hSendThread != 0) ::TerminateThread(m_hSendThread, 0);

    m_pSktServer->close();
    if(m_pSktClient != NULL) m_pSktClient->close();
    delete m_pSktServer;
    if(m_pSktClient != NULL) delete m_pSktClient;
}

void CKTVNetwork121::SendMsg(int MainID, int SubID, const char* pData, size_t size)
{
    if(NULL == m_pSktClient)
    {
        m_pSktClient = new zmq::socket_t(m_CtxClient, ZMQ_REQ);
        m_pSktClient->connect(m_szConnAddr.c_str());

        _send_param* sp = new _send_param();
        sp->cs = &m_CriticalSection;
        sp->queue = &m_SendQueue;
        sp->socket = m_pSktClient;

        m_hSendThread = ::CreateThread(NULL, 0, SendThread, sp, 0, 0);
    }

    /** 准备数据 */
    SendData sd;
    sd.header = new CKTVNetworkHeader();

    /** 数据头 */
    sd.header->MagiNum = MAGIC_NUM;
    sd.header->MainID = MainID;
    sd.header->SubID = SubID;
    sd.header->Size = size;

    /** 数据体 */
    sd.data = new char[size];
    memcpy(sd.data, pData, size);

    /** 加入队列 */
    ::EnterCriticalSection(&m_CriticalSection);
    m_SendQueue.push(sd);
    ::LeaveCriticalSection(&m_CriticalSection);
}

CKTVError.h

//////////////////////////////////////////////////////////////////////////
//
// KTV抛出错误类
//
// Program by 死月(XadillaX) (admin@xcoder.in)
// Create Date: 2011/08/25
//
//////////////////////////////////////////////////////////////////////////
#ifndef CKTVERROR_H
#define CKTVERROR_H

#pragma once
#include
using namespace std;

#define THROW_KTV_ERROR(num, what)      (throw CKTVError(num, what, __FILE__, __FUNCTION__, __LINE__))

class CKTVError
{
public:
    CKTVError(int num, string what, string file = "", string function = "", int line = 0) :
        num(num),
        what(what),
        file(file),
        function(function),
        line(line)
    {
    }
    virtual ~CKTVError(void);

    void                        Show();
    string                      ToString();

    int                         Num() { return num; }
    string                      What() { return what; }
    string                      File() { return file; }
    string                      Function() { return function; }
    int                         Line() { return line; }

private:
    int                         num;
    string                      what;
    string                      file;
    string                      function;
    int                         line;
};

#endif

CKTVError.cpp

#include "CKTVError.h"

CKTVError::~CKTVError(void)
{
}

void CKTVError::Show()
{
    printf("%s", ToString().c_str());
}

string CKTVError::ToString()
{
    char buf[2048];

#ifdef _DEBUG
    sprintf(buf, "[ERROR 0x%.8X]\nMessage:  %s\nFile:     %s\nFunction: %s\nLine:     %d\n\n", num, what.c_str(), file.c_str(), function.c_str(), line);
#else
    sprintf(buf, "[ERROR 0x%.8X] %s\n", num, what);
#endif

    return buf;
}

21 2011

新皮肤:xcoder-yomi放出半成品截图

  心血来潮突然想做皮肤,于是谨以此纪念亲爱的 谏山黄泉Sama 。 = = 大爱~

  然后LOGO小仿了一下《喰靈 – 零 -》的LOGO,右上角则是充满杀气的黄泉Sama。本次皮肤是我第一次做WP的皮肤,总体的色调偏暗,配色主要是采用了黄泉身上的元素,比如说服饰、头发、眼睛以及血痕。特意在皮的整体边缘留了一条白边,她的衣服上面也是有白边的。然后其实右下角其实有两条格力高(不知道算不算画蛇添足)。

  相信爱、能将所爱之人抹杀吗。将这句话生搬硬套放到banner的那一段代码里,并以“代码 斩杀 热爱”来点缀在右上角。

  不过因为是处女作,而且本人本身也不擅长设计,做的就马马虎虎,不一定比当下的皮肤好看,嘛,自己喜欢就好233~(*゚∀゚)っ

  接下去放PS图以及当前完成部分的Chrome下效果,各位酱表拍砖(≡ω≡.)||


9 2011

CLIC2C New vision!

到马德里也有一段时间了,在公司的工作很蛋疼的竟然是做网站而不是手机应用。明明是aquaMobile来着。看来人家的核心内容还不是随随便便能碰触到的。该好好努力,努力学点东西回去,就该好好表现自己,让他们觉得我能参与他们的CLIC2C项目。

先不说了,我过去先给他们改了一个首页,期间有仿的元素,但是大体自我感觉还是可以的。先上效果图:

 

具体的页面Demo我也已经上传到XCODER了,有兴趣的人可以点击以预览一下:

点我点我~!


16 2011

关于游戏资源包的更新、删除

  最近除了忙各种各样的期末考试、去西班牙的签证,就是公司的那个韩国项目了。

  我的任务基本完成——将原本单屏的游戏改成三屏,完善整个GUI系统以及“劫持”了原游戏中的一些逻辑,比如滚轴的排列可以任意控制等。由于原代码中的GUI系统没有文本编辑框,我还得自己写一个。然而我对于IME的操作、GDI和DX的结合不是非常熟悉,所以还是参照了一下ShowLong所修改的微妙的平衡给HGE写的中文解决方案。

  完成了以上的任务之后,由于我的考试以及签证问题以及我本身的任务差不多了,就把这个摊子就扔回公司去了。在交接的时候,老大给我派了一个任务,让我来写这个游戏资源包的代码。

  原版游戏代码中有资源包代码,但是写得非常乱,于是需要我来写一个新的文件结构、新的加密算法,然后仍然是“劫持”掉原代码中的资源包加载函数。

  在此之前,我拜读了云风的《游戏资源的压缩、打包与补丁更新》,有了点灵感。

  最主要的就是其删除这一块。为了让用户在更新的时候减少大量的文件IO操作,做法就是减少文件内容的大幅度移动。
› 阅读全文


26 2011

魔法少女まどか☆マギカ[魔法少女小圆] MAD分享

  这个MAD感觉灰常好,就我个人而言连看了五遍还不过瘾。刚看过的剧情历历在目,又煽情了。

   我把它发到56了,大家可以在下面看。如果想要原版[*.mp4]文件请在下面留邮箱,你懂的。



就你一个人吗?
神尾观铃