第九章:L2JMobius学习 – 角色创建讲解

在上一个章节中,我们已经介绍了GameServer中网络数据通信的基本流程。本章节,我们来介绍如何创建游戏角色。首先客户端会向服务端发送NewCharacter数据包,这个数据包会返回客户端CharTemplates数据包,这个数据包包含了游戏的基础职业信息(人类法师和战士,精灵法师和战士等等)。我们来看看NewCharacter数据包的内容。这个类不需要读取客户端发来的数据,只需要将“基础职业角色模板”数据发给客户端就行了。这些“基础职业模板”就是为了让玩家创建角色的。我们查看它的run方法,如下所示

// 向客户端发送 CharTemplates 数据包(基础职业角色列表)
final CharTemplates ct = new CharTemplates();
// 空的职业角色(没有使用)
PlayerTemplate template = PlayerTemplateData.getInstance().getTemplate(0);
ct.addChar(template);
// 人类战士
template = PlayerTemplateData.getInstance().getTemplate(ClassId.FIGHTER);
ct.addChar(template);
// 人类法师
template = PlayerTemplateData.getInstance().getTemplate(ClassId.MAGE);
ct.addChar(template);
// 精灵战士
template = PlayerTemplateData.getInstance().getTemplate(ClassId.ELVEN_FIGHTER);
ct.addChar(template);
// 精灵法师
template = PlayerTemplateData.getInstance().getTemplate(ClassId.ELVEN_MAGE);
ct.addChar(template);
// 黑暗精灵战士
template = PlayerTemplateData.getInstance().getTemplate(ClassId.DARK_FIGHTER);
ct.addChar(template);
// 黑暗精灵法师
template = PlayerTemplateData.getInstance().getTemplate(ClassId.DARK_MAGE);
ct.addChar(template);
// 兽人战士
template = PlayerTemplateData.getInstance().getTemplate(ClassId.ORC_FIGHTER);
ct.addChar(template);
// 兽人法师
template = PlayerTemplateData.getInstance().getTemplate(ClassId.ORC_MAGE);
ct.addChar(template);
// 矮人战士
template = PlayerTemplateData.getInstance().getTemplate(ClassId.DWARVEN_FIGHTER);
ct.addChar(template);
// 向客户端发送
client.sendPacket(ct);

上面的代码虽然多,但是非常简单,就是两个类,一个是CharTemplates数据包类,另一个是就每一个“基础职业角色模板”类PlayerTemplate。前者本质就是一个列表,我们查看一下,就知道这个CharTemplates数据包类里面有一个:List<PlayerTemplate> _chars  然后就是将PlayerTemplate类放入到这个列表中。因此,我们还是重点查看PlayerTemplate类,看看这个“基础职业角色模板”类是如何实例化,以及他的数据从哪里来的?

这个PlayerTemplate继承自CreatureTemplate类,这两个类非常简单,就是一些成员变量和getter/setter方法,说明他们只是一个“数据类”,并没有任何的功能。

private final int _baseSTR;			// 力量(影响物理伤害)
private final int _baseCON;			// 体质(影响HP)
private final int _baseDEX;			// 敏捷(影响速度和命中) 
private final int _baseINT;			// 智力(影响魔法伤害)
private final int _baseWIT;			//  智慧(影响施法速度和魔法抵抗)
private final int _baseMEN;			// 精神(影响MP)
private final float _baseHpMax;		// HP最大值
private final float _baseCpMax;		// CP最大值
private final float _baseMpMax;		// Mp最大值
private final float _baseHpReg;		// 当前HP值
private final float _baseMpReg;		// 当前MP值

private final int _basePAtk;		// 物理攻击力
private final int _baseMAtk;		// 魔法攻击力
private final int _basePDef;		// 物理防御力
private final int _baseMDef;		// 魔法防御力
private final int _basePAtkSpd;		// 物理攻击速度
private final int _baseMAtkSpd;		// 魔法攻击速度


private final Race _race;			// 种族
private final ClassId _classId;		// 职业
private final String _className;	// 职业名称
private final int _classBaseLevel;	// 基础等级
private final int _spawnX;			// 出生点X坐标
private final int _spawnY;			// 出生点Y坐标
private final int _spawnZ;			// 出生点Z坐标

以上是我们列举一些职业属性,当然还有其他的属性,这里就不介绍了。

template = PlayerTemplateData.getInstance().getTemplate(ClassId.FIGHTER); 

我们发现,PlayerTemplate类是由PlayerTemplateData类的getTemplate方法实例化的,参数就是职业枚举ID。ClassId是所有职业的枚举类型,有需要的话,大家可以点进去看看。PlayerTemplateData类从名称来看,它就是操作模板数据的类,它的主要功能就是从“data/stats/playerTemplates.xml”配置文件中读取职业模板数据。这个XML文件中记录了所有职业等级的数据,并将这些数据放入到 Map<Integer, PlayerTemplate> _templates 集合中。XML配置文件中的每一条纪录都对应了一个PlayerTemplate实例,其实就是将纪录中的属性值赋值给PlayerTemplate类中的成员变量,两者的名称是一一对应关系。有了这个集合之后,我们就可以根据职业枚举ID,获取对应的PlayerTemplate实例了。

在GameServer中,很多的数据操作,不管是读取XML,还是读取数据库表记录,基本都是将他们放到一个List或者Map这样的集合中,也就是缓存起来,后续再根据Id进行获取。这种方式可能占据大量内存,但是读取速度是非常快的(相对于读取硬盘上面的文件而言)。

接下来,玩家客户端接收到“基础职业角色模板”数据之后,玩家就可以创建游戏角色了。我们可以选择一个种族,选择一个职业,还可以选择性别,发型,发色,脸型,不要忘记输入角色昵称。角色创建完毕之后,会将这些数据发送给服务器端,对应的数据包是CharacterCreate。我们来查看一下这个数据包。

_name = packet.readString();			// 昵称
_race = packet.readInt();				// 种族
_sex = (byte) packet.readInt();			// 性别
_classId = packet.readInt();			// 职业
_int = packet.readInt();				// 智力
_str = packet.readInt();				// 力量
_con = packet.readInt();				// 体质
_men = packet.readInt();				// 精神
_dex = packet.readInt();				// 敏捷
_wit = packet.readInt();				// 智慧
_hairStyle = (byte) packet.readInt();	// 发型
_hairColor = (byte) packet.readInt();	// 发色
_face = (byte) packet.readInt();		// 脸型

以上就是我们通过read方法读取到的数据。接下来就是run方法里面的内容。在run方法中,会检查角色的昵称,检查的规则里面不允许中文。如果我们想要输入中文昵称的话,就需要把这个检查规则修改一下(最直接的方式就删除掉这些验证规则代码)。接下来,还会继续检查,比如玩家创建所有角色的数量,这里不能超过7个(配置文件中可以调整);还要检查角色昵称不能重复;最后就要创建一个Player类,这个Player类就代表了游戏角色对象,是一个非常重要的类。Player类的实例化非常的繁琐,因为涉及到它的父类,以及其他数据类的实例化,我们目前暂时不讲解这个过程。同时,还会将其保存到characters 数据表。这个characters数据表就是用来保存玩家角色的。

// 创建新角色的ID
final int objectId = IdManager.getInstance().getNextId();
// 创建玩家角色类Player(保存到 characters 数据表)并设置HP,CP,MP
newChar = Player.create(objectId, template, client.getAccountName(), _name, _hairStyle, _hairColor, _face, _sex != 0);
newChar.setCurrentHp(newChar.getMaxHp());
newChar.setCurrentCp(0);
newChar.setCurrentMp(newChar.getMaxMp());

// 发送角色创建成功数据包
client.sendPacket(new CharCreateOk());

// 初始化游戏角色,并且保存到数据库中
initNewChar(client, newChar);

发送角色创建成功数据包CharCreateOk类,这个类非常的简单,不需要传递数据,它就是一个固定的成功标识而已,我们就不详细说了。我们重点说一下initNewChar角色初始化。这个初始化的过程,包括给新手玩家赠送金币(博主给大家赠送的是10万金币),还赠送一些基础装备(见习生的装备),还可以自定义出生点(默认即可),还会初始化 Shortcuts(把一些动作快捷方式放到Shortcuts上面,把一些物品和技能放到Shortcuts上面)。然后调用 newChar.store() 方法将这些初始化的信息保存到数据库中。这里不同的数据会保存到不同的数据表中,因为涉及的内容太多,所以我们也不详细介绍。大家明白这个过程就行了。

最后向客户端发送CharSelectInfo 数据包,这个数据包包含了玩家的所有角色。

// 发送角色列表数据包
final CharSelectInfo cl = new CharSelectInfo(client.getAccountName(), client.getSessionId().playOkID1);
client.sendPacket(cl);
client.setCharSelection(cl.getCharInfo());

因为我们刚刚只创建了一个游戏角色,因此这个数据包中只有一个游戏角色。如果我们继续创建新的角色,那么返回的就是多个角色。玩家就会在客户端看到自己创建的所有角色,然后选择其中一个角色,就可以进入游戏世界了。我们还是看看这个CharSelectInfo数据包。这个数据包类中有一个List<CharSelectInfoPackage> _characterPackages;列表,然后列表元素就是CharSelectInfoPackage类,它就代表了玩家创建的每一个游戏角色。为什么不使用Player类作为列表元素呢?因为Player类类太多于庞大,它适合进入游戏世界后角色对象。对于没有进入游戏世界,而是选择游戏角色的界面而言,使用一个小小的CharSelectInfoPackage类就能完成这个任务了。这个CharSelectInfoPackage类也是一个“数据类”,里面包含了一些角色的基础数据,没有任何的额外功能。这个CharSelectInfoPackage类里面有一个private final int[][] _paperdoll二维数组,它代表了该角色身体上穿着的装备。这个数据来源于角色的“背包”,而“背包”里面的物品则最终来源于数据表“items”。请注意,这里的“背包”不仅仅包含玩家角色游戏里面的“包裹”,还包括玩家角色身上的物品。因此在数据表“items”中会有一个字段“loc”,该字段值为“PAPERDOLL”的时候,就代表身上的物品,而字段值为“INVENTORY”的时候,就代表“包裹”里面的物品。最后说明一下,CharSelectInfoPackage类就是从数据表 characters 读取而来的。

接下来,客户端收到CharSelectInfo数据包之后,就会展示玩家创建的所有角色。然后,玩家就要选择一个角色,就会向服务器端发送CharacterSelected数据包。这个数据包里面就包含了角色的ID,然后就会调用private static Player restore(int objectId) 方法来实例化Player对象,其实就是从数据表characters中读取数据。当然,这个过程也是比较复杂的。当我们完成Player对象实例化之后,就会向客户端发送CharSelected数据包。

// 从数据库载入Player对象
final Player cha = client.loadCharFromDisk(_charSlot);

cha.setClient(client);
client.setPlayer(cha);
client.setConnectionState(ConnectionState.ENTERING);
client.sendPacket(new CharSelected(cha, client.getSessionId().playOkID1));

注意,上面的代码,实例化好的Player对象会被GameClient对象持有。这个非常的重要,因为后续的很多操作,就需要Player对象,我们只需要从GameClient对象获取就行了。这个CharSelected数据包也非常的简单,里面只是包含了角色的基础信息。在这个数据包中,会有一个特别的数据,代码如下:

GameTimeTaskManager.getInstance().getGameTime()

这个是GameServer服务运行的时间,相当于游戏世界里面的时间。

接下来,服务器端也准备好了(实例化了Player对象),客户端就向服务器端发送EnterWorld

数据包,告诉服务器端,我要进入游戏世界了。我们下一章介绍。

本章节涉及的内容均已上传百度网盘:

https://pan.baidu.com/s/1XdlcCFPvXnzfwFoVK7Sn7Q?pwd=avd4

欢迎加企鹅交流裙:874700842(裙文件里面也可以下载所有内容)。

猜你喜欢

转载自blog.csdn.net/konkon2012/article/details/131635900