不废话,开始分析FantasyWarrior3D结构

假设大家都了解Cocos(3.3rc0),以及用过Cocos Code IDE(1.01)。这篇文章主要的内容是这个游戏的的实现思路,具体细节会暂时(就)跳(是)过(懒)。

游戏总共有四个场景,分别是

图1-1

我们着重讲一下BattleScene这个场景的结构

BattleScene的层次

图2-1

这个层次还是比较简单的。一个Scene加一个Layer,其他元素都是AddChild到Layer上,包括英雄和怪物。其中比较特殊的是camera,这是一个摄像机对象,用来控制画面远近、视角等,另外uiLayer是挂在camera上的(我们在摄像机跟踪一节解释这个做法)

然后绝大部分逻辑由gameController这个定时器函数来控制,其中包括碰撞检测、攻击指令、摄像头跟踪、以及游戏主逻辑。这个定时器函数每一帧都会被引擎调用。

图2-2

最后currentLayer支持触摸,这个用来触发英雄的特殊技能和控制镜头。

碰撞检测

collisionDectect是gameController内第一个被调用的函数,用来检查所有游戏中的所有角色。如果角色离的太近,会让他们’推‘开;如果超过游戏场景边缘,会把他们’挪‘回来。

每一个游戏角色有一个圆形区域,这个区域大小由_radius决定;另外它有重量,由_mass决定;

所有有效的游戏角色都被放入了HeroManager和MonsterMananger内,所以碰撞检测的逻辑就是:取出每一个角色,与剩下的角色进行计算距离,小于两者半径之和判定为发生‘碰撞’;按两者中心点的直线方向,计算应该后退的距离,设置新的位置坐标(由重量决定后退的比例)。

碰撞检测结束后,会顺便检测角色是否超过预先设定的场景边缘位置,这在isOutOfBound内实现。

注意,碰撞检测不会处理已经‘死亡’的游戏角色,碰撞检测中HeroManager和MonsterManager会移除死亡的游戏角色。

摄像机跟踪

在我们的游戏中,摄像机有两种跟踪方式:

第一种跟随三个主角所在位置自动移动,同时根据玩家手指移动稍微改变位置,第二种是游戏角色释放特殊技能时切换特殊的视角。

这两种摄像机跟踪都在同一个函数moveCamera内实现,核心逻辑是通过setPosition3D不断改变摄像机的位置,同时通过lookAt让摄像机‘看’向正确的位置。你可以想象一支笔的两端,一端是摄像头位置,一端是它‘看’的位置。

另外,由于摄像机位置需要从A点改变到B点,看的位置从a点到b点,我们不能直接setPosition3D(B)和lookAt(b),而是需要计算从A到B,以及从a到b的差值位置。在游戏中我们通过cc.pLerp(A, B)来计算得到需要逐步移动的位置。

在BattleScene的onTouchMoved函数内,玩家通过触摸改变cameraOffset的值,摄像机位置会随这个值改变,于是实现了玩家在限制范围内控制摄像机位置的功能。

最后,我们把uiLayer挂在摄像头上(addChild),就是由于摄像机位置一直变化。如果放在currentLayer上,摄像机一移动,UI界面就看不到了。现在uiLayer会随着摄像机移动改变位置,所以我们就能一直看到UI界面,这就像你带了一副眼镜,不管你怎么转头,眼镜架还是在你鼻梁上。

最后用几张图来表示摄像机在3D空间的位置和移动的方式

图2-3

左图是引擎默认情况下,摄像机的位置。左图的绿色红色箭头是x轴,蓝色箭头是y轴,绿色箭头为z轴。通过阅读Camera::initDefault源码,可以知道,在3D模式下,摄像机是位于(winSize.width/2, winSize.height/2, getZEye),并向(winSize.width/2, winSize.height/2)方向看。

右图是游戏中摄像机的位置,在运行时,它会根据英雄当前所在的位置不断调整x轴和y轴坐标,而z轴坐标不变。

UI层

这一层用来显示游戏角色头像、血量、愤怒值,角色受到攻击头像会震动,角色死亡后会变成黑白色,可以触摸角色头像触发特殊技能攻击敌人,胜利时显示成功界面。

值得一说的是,我们把UI层逻辑和游戏逻辑拆分开,用注册消息\函数回调的方式来解决数据传输的问题。

在BattleScene中,我们注册了BLOOD_MINUS,ANGRY_CHANGE消息,用来接收血量变化、愤怒值变化,通过回调函数boodMinus,angryChange,调用uiLayer改变血量,愤怒值。

角色类结构

游戏角色类都放在actors文件夹下,它们的关系如下

图2-4

每个游戏角色在创建的时候,都拥有一个自身update函数,运行时每帧调用一次。这个update函数负责调用如下逻辑:

图2-5

baseUpdate函数,它负责定时执行角色的AI逻辑(具体内容我们在角色AI一节解释)。

stateMachineUpdate函数,它负责根据目前角色状态值让角色表现对应状态的动作、动画。

movementUpdate函数,它负责两件事情,一是在不断改变自己的位置,让角色在游戏中移动;二是改变旋转角度,让角色面向目标方向。

角色的状态机

角色的状态在GlobalVariables.lua文件中定义,由下图几个状态组成

图2-6

角色的状态被stateMachineUpdate控制,不断的在以上六种状态中切换,切换的逻辑如下图:

图2-7

walkUpdate的逻辑是:如果角色已经有一个目标(角色AI中实现),如果目标在攻击范围内,就切换到attackMode;如果不在攻击范围,继续移动过去。如果没有目标,继续往右走,如果右边不能再走了,就停在那里发呆(移动在movementUpdate里实现)。

attackUpdate的逻辑是:每次攻击需要花一定时间,首先判断是否到了下一次攻击的时间;如果是,根据概率发出普通攻击或者特殊攻击,然后播放攻击动画。特殊攻击的时候做三件事情,一、除了发技能的角色意外的游戏画面全部暗下来(通过发送SPECIAL_PERSPECTIVE消息,调用回调函数实现);二、让摄像头移动,形成特写效果;三、通过setTimeScale降低scheduler的速度,形成慢动作效果。此外无论是特殊攻击还是普通攻击,会生成攻击环(这个在攻击指令一节中说明)。

knockingUpdate的逻辑是:每次被击中需要一定时间播放受击动画,首先判断被攻击动画是否播放结束;如果是,判断目标是否在自己的攻击范围内,如果是,进入attackMode,如果不是,切换到walkMode。

角色AI

角色的AI是被baseUpdate驱动的,每个角色有自己的_AIFrequency。所以AI的基本逻辑是:首先判断是否到了下一次执行AI的时间,如果是,执行AI。

AI逻辑主要是寻找攻击目标,如果没有找到目标,根据当前状态切换到walkMode或者idleMode;如果找到目标,判断目标是否在自己的攻击范围内,如果是,切换到attackMode,如果不是,切换到walkMode去靠近目标。

攻击指令

攻击指令是一个独立类,它的baseClass是BasicCollider,结构如下:

图2-8

其中的sp变量就是游戏中我们看到的冰锥、冰球、箭支,分别是一个sprite或者sprite3D对象实例。

但是真正起到攻击作用的,是BasicCollider的攻击区域,它可能是一个扇形、圆形或者圆环,如图

minRange决定攻击最小半径,可以为0;maxRange大于minRange;angle决定了扇形圆心角,值的范围在(0, 360];facing决定方向;这几个属性决定了攻击区域的大小。例如A和B,唯一的差别是A的minRange=0,当目标在A或者B区域内,都会受到伤害。

攻击指令在创建时,就被加入到AttackManager里(BasicCollider:initData)。而solveAttacks(还记得在哪里调用吗)中会循环遍历AttackManager,每次取出一个攻击指令,判断它与游戏角色是否发生碰撞,如果没有,就继续与其他游戏角色比较;如果有,调用onCollide,播放攻击特效、声效,调用被攻击角色的hurt函数;在循环结束前,从AttackManager移除已经无效的攻击指令(无效是指curDuration > duration)。

攻击指令中还有两种特殊指令,一种是可以移动的攻击指令,比如飞行的冰球、箭支,一种是DOT(Damage over time,在一段时间内不断对目标造成伤害),只有这种指令才有curDOTTime和DOTTimer属性。在solveAttacks的循环结束前,有一个attack:onUpdate函数被调用,这个函数会不断改变攻击指令的位置,以实现‘飞行’的效果;另外,这函数会改变curDOTTime的值,这样,当curDOTTime > DOTTimer时,onCollide就会对处于攻击范围内的目标产生伤害。

游戏主逻辑

主逻辑靠GameMaster:update驱动,这个函数在BattleScene中gameController中被调用。

游戏主逻辑包含二个功能:第一,创建英雄和怪物;第二,在合适的时机显示怪物和界面。

值得一说的是,在游戏进行中,创建怪物会导致游戏卡顿,所以我们在创建的时候就把所有的怪物放在了对应的pool里,同时也addChild到了currentLayer上,同时设置为隐藏;在游戏进行中,我们才把pool里的怪物拿出来,放到MonsterManager里面。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • This article is a record of my journey to learn Game Deve...
    蔡子聪阅读 3,746评论 0 9
  • 本教程旨在教会大家如何为YGOPro编写或修改一个AI脚本。前几节是新手指引,而接下来的章节涵盖了一些更深入的知识...
    e26h阅读 12,944评论 2 9
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,361评论 25 707
  • 111. [动画系统]如何将其他类型的动画转换成关键帧动画? 动画->点缓存->关键帧 112. [动画]Unit...
    胤醚貔貅阅读 12,936评论 3 90
  • 假如我能遇见你 也是不言语 可我只看见了自己 便有了满腹言语 转角低头千百次 低下的却是勇气
    风起2015阅读 197评论 3 1