《猎头专家》地形和背景实现
本文作者:
射手座团队
蟹老板
在《猎头专家》里,站得高不一定是好事,掉坑里也不一定是坏事,大家应该都体会到了,地形的重要性不言而喻,今天就来聊聊地形在《猎头专家》里的实现。
设计目标
- 实现容易(简单)
- 生成灵活(新奇)
- 难度可控(又好玩)
设计思路
地形在游戏的战斗场景中长度固定且需要考虑画面外的碰撞,所以我们的地形没有使用渐进式生,而是在战斗场景初始化时就全部生成完成,在线对战时,会预先生成地形数据发送给双方。
地形的组成
对于 size 这个单位,1 代表 128x128 像素的矩形贴图。
- **平路:**type=0,size:1x1,插件:RigidBody, PhysicsBoxCollider
- **小坡:**type=1,size:3x2,插件:RigidBody, PhysicsPolygonCollider 红色的圆点是顶点,组成多边形碰撞盒,顶点越多越平滑,当然性能也会受影响
- **大坡:**type=2,size:4x3,插件:RigidBody, PhysicsPolygonCollider 红色的圆点是顶点,组成多边形碰撞盒,顶点越多越平滑,当然性能也会受影响
坡路的设计思路
- 坡的角度为45度,这样保证了物体滑落时的感觉比较自然。
- 坡底和坡顶拐角处都包含了一块平路并且设置了2个顶点,确保物体滑落时的平滑感。
- 坡度和坡顶都是平路可以和”平路“地形无缝连接。
地形数据格式
从下面的配置文件片段中可以看出,如果type为负值,则表示翻转,那么 上坡 素材就会变成 下坡 素材。
1[
2 {"type": 0, "x": 0, "y": 0},
3 {"type": 0, "x": 28, "y": 0},
4 {"type": 0, "x": 256, "y": 0},
5 {"type": 1, "x": 384, "y": 0},
6 {"type": 0, "x": 768, "y": 128},
7 {"type": -1, "x": 896, "y": 0}
8]
地形的控制
地形组件有一下几个参数:
- 战场宽度:目前是一个固定宽度8000px
- 最大高度:地形顶部可触及高度
- 最小高度:地形底部最低高度
- 最大连续平路:连续平路的最大数量
- 最小连续平路:连续平路的最小数量
参数2、3控制地形的最大落差,落差越大越可能出现大坑。
参数4、5控制平路的最小与最大长度,平路少,则容易掉沟里,有利有弊吧,这两组参数决定了地形的难度,坡路地形会根据高度的变化自动翻转,实现上坡和下坡的无缝连接。
地形的美化
刚才提到的地形生成,仅仅填充了角色脚下的那一排地面。还有很多空白区块需要填充,我们用一个 size 1x1
的地形同色纹理来补充,并随机补上一些装饰用的纹理。
不管是地形组件还是地形纹理,都按颜色风格分组为多个plist,在战场生成时,随机调用。
最后附上战场地形实现示意图
初始地图:没有经过纹理填充时
美化过的地图
为优化填充性能,填充是按自上而下找出整块区域使用平铺的方式填充,以节省Sprite的使用数量。
gound.ts 部分代码实现
1@ccclass
2export default class Ground extends cc.Component {
3 // 纹理的 plist
4 atlas: cc.SpriteAtlas = null
5 // 纹理组件
6 tile: cc.Prefab = null
7 // 平路组件
8 flat: cc.Prefab = null
9 // 小坡组件
10 ramp1: cc.Prefab = null
11 // 大坡组件
12 ramp2: cc.Prefab = null
13
14 @property(cc.Integer)
15 sceneWidth: number = 8000
16 @property(cc.Integer)
17 maxHeight: number = 5
18 @property(cc.Integer)
19 minHeight: number = -2
20 @property(cc.Integer)
21 maxStraightParts: number = 4
22 @property(cc.Integer)
23 minStraightParts: number = 2
24 // 起始方向:1为朝右(正),-1为朝左(反)
25 @property(cc.Integer)
26 direction: number = 1
27 // 起始高度(台阶)
28 @property(cc.Integer)
29 stairs: number = 0
30
31 // 每个体积单位的实际像素
32 sizeUnit = 128
33
34 onLoad () {
35 // 地形使用了缓存类管理(内部使用的是cc.NodePool)
36 this._nodeCacheManager = new NodeCacheManager()
37 }
38
39 // 设置纹理主题(配合背景),之后动态更换组件的 spriteFrame
40 setAtlas (atlas) {
41 this.atlas = atlas
42 }
43
44 // 根据server发来的地形数据动态生成
45 onSyncGround (groundData: any[]) {
46 for (let i = 0; i < groundData.length; i++) {
47 // 取出每一块的数据
48 let prefabData = groundData[i]
49 // 根据地形类型取出组件并实例化到界面上
50 let prefab = this._getPrefab(prefabData.type)
51 let node = cc.instantiate(prefab).addTo(this.panel, -1)
52 node.getComponent(cc.Sprite).spriteFrame = this.atlas.getSpriteFrame(prefabData.type)
53 // 坡路可能需要翻转
54 if (prefab.size.h > 0) {
55 node.scaleX = prefabData.type > 0 ? 1 : -1
56 }
57 node.x = prefabData.x
58 node.y = prefabData.y
59
60 // 每铺一块地形,计算出下块地形坐标
61 this.startX += prefab.width * this.sizeUnit
62 // 如果是反向,则坡路由于锚点问题会造成偏移,所以此处修正一下
63 if (this.direction < 0 && prefab.size.h > 0) {
64 node.x = this.startX
65 }
66 // 根据当前地形坐标及大小创建它下面的装饰纹理
67 this.createTiles(prefab.size.w, prefab.size.h, node.x, node.y)
68 // 超过屏幕宽度后退出
69 if (this.startX > this.sceneWidth) {
70 break
71 }
72 }
73 // 地形创建完成后清除缓存
74 this._nodeCacheManager.clear()
75 }
76
77 // 平铺空白处
78 createTiles (w, h, x, y) {
79 // 在每块地形底部向下铺一块纹理(不超过minHeight)
80 // ...
81 // 随机创建其它风格样式砖块
82 // ...
83 }
84
85 // 以地形最底部的y点(即 minHeight 之下)算出最大平铺纹理区域
86 private _createMainTile (y: number = 0) {
87 let stails: number = Math.floor(y / 128)
88 let mainTile = this._nodeCacheManager.createNode(this.tile)
89 let sprite = mainTile.getComponent(cc.Sprite)
90 sprite.spriteFrame = this.atlas.getSpriteFrame('tile')
91 let height: number = (5 + stails) * this.sizeUnit
92 mainTile.setContentSize(cc.size(this.sceneWidth, height))
93 mainTile.x = 0
94 mainTile.y = y + this.minHeight * this.sizeUnit - height
95 this.panel.addChild(mainTile, -2)
96 }
97}
全文完
- 文章ID:2669
- 原文作者:zrong
- 原文链接:https://blog.zengrong.net/post/youshootfirst4/
- 版权声明:本作品采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可,非商业转载请注明出处(原文作者,原文链接),商业转载请联系作者获得授权。