14. ORM Part 3: 接口类¶
根据 ORM Part 2: 定义对象 的说明,定义 Object Model和建立的话iFun Engine的ORM自动形成Class。 这叫 接口类
假设项目的名称是 {{ProjectName}} , 使用JSON定义的对象模型名称是 {{ObjectName}} ,包括接口类自动形成的文件如下。
src/{{ProjectName}}_object.h
src/object_model/{{ProjectName}}_object.cc
src/object_model/common.h
src/object_model/{{ObjectName}}.h
mono/{{ProjectName}}_object.cs
Note
自动形成的文件不是在建立的目录保存,而是在资源目录下保存。这是为了像 Visual Studio 一样支持自动完成的IDE环境。
Warning
Interface class 只能在 活动线索 动作。不能在其他线索使用。
14.1. 接口类的方法¶
本部门的说明假设如下对象模型。
"ObjectName" : {
"KeyAttribute1Name" : "KeyAttribute1Type Key",
"KeyAttribute2Name" : "KeyAttribute2Type Key",
"Attribute3Name" : "Attribute3Type",
"Attribute4Name" : "Attribute4Type"
}
Note
KeyAttribute1Type, KeyAttribute2Type, Attribute3Type, Attribute4Type 意味着基本类型或者其他模型的名称。
Note
下面参数目录中参数后边的 ROLLBACK
意味着在调用该参数时有可能会发生ROLLBACK。 具体的内容请参考 事务回滚
14.1.1. 在数据库(DB)形成对象¶
产生对象的method形成 Create(…) 的名称。 如果存在由Key指定的属性的话,为了获得把Key属性作为参数的形式,产生代码。
static Ptr<ObjectName> Create(const KeyAttribute1Type &key1_value,
const KeyAttribute2Type &key2_value) ROLLBACK;
public static ObjectName Create(KeyAttribute1Type key1_value,
KeyAttribute2Type key2_value) ROLLBACK;
Important
当已有由Key指定的属性时,如果有一个已经具有该Key值的对象的话,则产生失败并返回NULL值。
下面的例子中 ch1 和 ch2 会正常产生,但是, 因为ch3 Key属性的Name值跟 ch1 一样, 所以产生失败并返回NULL。
1 2 3 4 5 6 | Ptr<Character> ch1 = Character::Create("legend");
Ptr<Character> ch2 = Character::Create("killer");
// 因为 ch1 已经产生了以 "legend" 作为 Key 属性的 Name, 因此下面的 ch3 就返回NULL。
Ptr<Character> ch3 = Character::Create("legend");
BOOST_ASSERT(not ch3);
|
1 2 3 4 5 6 | Character ch1 = Character.Create ("legend");
Character ch2 = Character.Create ("killer");
// 因为 ch1 已经产生了以 "legend" 作为 Key 属性的 Name, 因此下面的 ch3 就返回NULL。
Character ch3 = Character.Create ("legend");
Log.Assert (ch3 == null);
|
14.1.2. 在数据库读取对象¶
读取对象的方法(Method)是分成两个。 一个是 Fetch(…), 另一个是 FetchBy…(…) 。 前者是跟样式无关当对象ID已经知道的情况下读取对象,后者是在模型定义文件中使用由Key指定读取对象的情况。 并且,产生读取每个对象和通过 batch 工作读取多个对象情况的方法。
14.1.2.1. 使用对象 ID 读取一个对象¶
使用下面的 Fetch(…) 参数可以读取一个对象。 如果对象不存在的话,就返回null。
static Ptr<ObjectName> Fetch(
const Object::Id &id,
LockType lock_type = kWriteLock) ROLLBACK;
public static ObjectName Fetch(
System.Guid object_id,
funapi.LockType lock_type = funapi.LockType.kWriteLock) ROLLBACK;
14.1.2.2. 使用对象 ID 读取多个对象¶
使用下面的 Fetch(…) 参数可以一次读取多个对象。
如果对象不存在的话,在**result*上该ID对应的值成为null。
static void Fetch(
const std::vector<Object::Id> &ids,
std::vector<std::pair<Object::Id, Ptr<ObjectName> > > *result,
LockType lock_type = kWriteLock) ROLLBACK;
如果对象不存在的话,在返回的Dictionary上该对象值成为null。
public static Dictionary<System.Guid, ObjectName> Fetch(
SortedSet<System.Guid> object_ids,
funapi.LockType lock_type = funapi.LockType.kWriteLock) ROLLBACK;
14.1.2.3. 使用Key属性读取一个对象¶
如果在对象模型的JSON里有由key指定的属性的话,产生如下方法(method)。 这样的话,能读取跟 Key 值一致的对象。 如果对象不存在的话,就返回 null。
下面是由 Key 指定的 KeyAttribute1Name 的情况下自动产生的方法。
static Ptr<ObjectName> FetchByKeyAttribute1Name(
const KeyAttribute1Type &value,
LockType lock_type = kWriteLock) ROLLBACK;
public static ObjectName FetchByKeyAttribute1Name(
KeyAttribute1Type value,
funapi.LockType lock_type = funapi.LockType.kWriteLock) ROLLBACK;
14.1.2.4. 使用Key属性读取多个对象。¶
跟上面的参数一样,但是可以一次读取多个对象。
如果对象不存在的话,在**result*上该Key对应的值成为null。
static void FetchByKeyAttribute1Name(
const std::vector<KeyAttribute1Type> &values,
std::vector<std::pair<KeyAttribute1Type, Ptr<ObjectName> > > *result,
LockType lock_type = kWriteLock) ROLLBACK;
如果对象不存在的话, 在返回的Dictionary上该对象值成为null。
public static Dictionary<KeyAttribute1Type, ObjectName> FetchByKeyAttribute1Name(
SortedSet<KeyAttribute1Type> values,
funapi.LockType lock_type = funapi.LockType.kWriteLock) ROLLBACK;
14.1.2.5. Fetch 的时候对象 cache¶
iFun Engine 的 ORM 为了提高性能,在 cache 储存对象和再使用。 考虑到 Cache,ORM 读取对象的循序如下。
在ORM 管理的 cache储存器里有对象的话,从cache读取。
在其他服务器 ORM cache上有对象的话,会发送 RPC 后,读取对象。
对象没有在任何服务器的 cache里的话,从数据库读取后保存到 ORM cache。
在数据库也不存在的对象的话,就返回NULL。
Note
想理解 iFun Engine 什么时候在 cache 里删除对象的话,请您参考 贮藏数据 。
14.1.2.6. 当 Fetch 时 LockType¶
LockType
如下3种类型。
kWriteLock
: 修改对象属性的一部分或者删除对象的时候使用。 锁定的时候,其他地方不能读取和使用对象。kReadLock
: 只能读取对象值的时候使用。 锁定的时候,其他地方可以读取该对象,但不能使用。kReadCopyNoLock
: 不是锁定而是创建副本后工作。因为在副本进行写作没有意义,因此副本是只阅读专用和不能使用写作。其他地方可以对该对象进行写作。在一秒之内短时间对象被改变也不会出现问题的情况下很有效率。 举个例子,表示朋友最后登入时间的话,锁定每个朋友对象不如使用副本。虽然使用副本储存器使用量会多,但是可以免于锁定产生的串行化。
Important
虽然 kWriteLock
是基本值,为了提高性能我们建议必要的情况下采用,其他情况下最好使用, kReadLock
或者 kReadCopyNoLock
例子) 通过 Key 属性,使用 Name 读取:
Ptr<Character> ch = Character::FetchByName("legend")
Character ch = Character.FetchByName("legend");
例子) 使用 Key 属性,但是为了一次读取多个对象和提高性能使用 kReadCopyNoLock
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | std::vector<string> names;
names.push_back("name1");
names.push_back("name2");
names.push_back("name3");
std::vector<std::pair<string, Ptr<Character> > > object_pairs;
Character::FetchByName(names, &object_pairs, kReadCopyNoLock);
for (size_t i = 0; i < object_pairs.size(); ++i) {
const string &name = object_pairs[i].first;
const Ptr<Character> &ch = object_pairs[i].second;
if (ch) {
...
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | SortedSet<string> names = new SortedSet<string>();
names.Add("name1");
names.Add("name2");
names.Add("name3");
Dictionary<string, Character> object_dict = null;
object_dict = Character.FetchByName(
names, funapi.LockType.kReadCopyNoLock);
foreach(KeyValuePair<string, Character> object_pair in object_dict)
{
if (object_pair.Value) {
...
}
}
|
Tip
对使用锁定改进性能的具体说明, 请您参考 使用正确地锁定类型 。
14.1.3. 在数据库删除对象¶
在数据库删除对象使用如下方法。
void Delete();
public void Delete();
如果, 删除的对象把其他对象当作属性的时候,属性的其他对象也一起被删除。 但是,根据在 Attribute 的 Flag 说明, 指定 Foreign
的话,不是所有关系,而不会一起被删除。
C++ 活着 C#的语法上,删除对象也不能自动把包含对象的变数作为 null。 具体内容请您参考如下 检查 NULL 。
14.1.4. 检查 NULL¶
对象一直 Ptr<ObjectName> 的形式来使用。 通过跟 ObjectName::kNullPtr 或者 Ptr<ObjectName>() 比较,可以判断 Ptr<ObjectName> 是否 NULL。
如果 Ptr<ObjectName> 不是 ObjectName::kNullPtr 的话, 这意味着该指针有效。 但是刚才在数据库删除对象的话,发生指针有效但对象不存在的情况。 通过如下方法可以确认对象是否存在。
bool IsNull() const;
对象总是写成 ObjectName 类型的变数形式。 使用 C# 的 null 可以确认该变数是否 null 。
ObjectName 的变量不是 null 的话, 这意味着该指针有效。 但是刚才在数据库删除对象的话,发生指针有效但对象不存在的情况。 通过如下方法可以确认对象是否存在。
public bool IsNull();
例子
1 2 3 4 5 6 7 | Ptr<Character> ch = Character::FetchByName("legend");
if (not ch) {
return;
}
ch->Delete();
BOOST_ASSERT(ch->IsNull());
|
1 2 3 4 5 6 7 | Character ch = Character.FetchByName("legend");
if (ch == null) {
return;
}
ch.Delete ();
Log.Assert (ch.IsNull());
|
14.1.5. 返回对象ID¶
所有对象都有固有 ID 值。 这 ID 的形式是 UUID,可以获得如下。
const Object::Id &Id() const;
public System.Guid Id;
因为所有对象都有固有ID, 对象模型没有 Key 属性也通过默认的 Fetch(...)
从数据库可以读取对象。
14.1.6. 读取/写作对象属性¶
凭属性产生 getter 和 setter。 Getter 的话,在属性前边有 Get 并有该属性的类型为返回值。 Setter 的话,在属性前边有 Set 并有该属性的类型为因数。
下面是 AttributeName3 的情况。
Attribute3Type GetAttribute3Name() const;
void SetAttribute3Name(const Attribute3Type &value);
public GetAttribute3Type GetAttribute3Name();
public void SetGetAttribute3Name(Attribute3Type value);
例子) 如果经验值为100以上的话,升级后经验值进行初始化
1 2 3 4 5 6 7 8 9 10 11 | Ptr<Character> ch = Character::FetchByName("legend");
if (not ch) {
return;
}
string char_name = ch->GetName();
if (ch->GetExp() > 100) {
ch->SetLevel(ch->GetLevel() + 1);
ch->SetExp(0);
}
|
1 2 3 4 5 6 7 8 9 10 11 | Character ch = Character.FetchByName ("legend");
if (ch == null) {
return;
}
string char_name = ch.GetName ();
if (ch.GetExp () > 100) {
ch.SetLevel (ch.GetLevel() + 1);
ch.SetExp (0);
}
|
Note
如果变更对象的属性,变更的内容在 ORM cache 和数据库上都会反映。如果该对象是从其他服务器 ORM cache 读取的对象的话,该服务器的 ORM cache 会更新。
14.1.6.1. 没有定义为 Foreign
的数组或者Map¶
没有指定为 Foreign 的数组和Map产生如下 Getter。
Get{{AttributeName}}(): Fetch 所有元素对象后 返回
ArrayRef<Ptr<{ObjectType}> >
或者MapRef<{KeyType}, Ptr<{ObjectType}> >
14.1.6.2. 定义为 Foreign
的数组或者Map¶
指定为 Foreign 的数组或者 Map 产生如下两种类型。
Get{{AttributeName}(): 不 Fetch 元素对象把对象 ID 值
返回为
ArrayRef<Object::Id>
,MapRef<{KeyType}, Object::Id>
。
Fetch{AttributeName}(): Fetch 所有元素对象返回
ArrayRef<Ptr<{{ObjectType}}> >
或者MapRef<{{KeyType}}, Ptr<{{ObjectType}}> >
14.1.7. 保存的对象 refresh¶
iFun Engine 的 ORM 不是在多种服务器环境下通过数据库分享数据,而是 使用 RPC 读取在远程服务器 caching 的对象,通过这种方式会减少数据库的负荷并分享服务器之间的数据 。
因此, 通过使用 ORM的 Create(...)
来产生或者使用 Fetch(...)
/ FetchBy...(...)
来调用的对象只能在事件处理程序里有效。则,在事件处理程序里可以保证该对象的使用,但是离开事件处理程序范围,从外部可以访问该对象。于是,iFun Engine 建议产生和访问对象的时候,记住对象 ID 或者 key属性 在事件处理程序里作 Create(…) / Fetch(…) / Fetch…(…) 。
但是按照情况有可能会把对象存在全程变量后从多个地方访问。在这种情况下使用如下两个方法可以 refresh 对象。
bool IsFresh() const;
bool Refresh() ROLLBACK;
public bool IsFresh();
public bool Refresh(funapi.LockType lock_type) ROLLBACK;
IsFresh()
不用 Refresh 该对象可以访问的话就返回 true
。如果 IsFresh()
参数返回 false
的话, 一定调用 Refresh()
参数后使用。
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | std::map<string /*account id*/, Ptr<Character> > g_characters;
void OnLevelRequested(const Ptr<Session> &session, const Json &message) {
string account_id = message["account_id"].GetString();
Ptr<Character> ch = g_characters.find(account_id);
// 先确认这对象能否在本 Message Handler 访问后,不能访问的话,调用 Refresh()。
if (not ch->IsFresh()) {
ch->Refresh();
}
Json response;
response["level"] = ch->GetLevel();
session->SendMessage("level", response);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | Dictionary<string, Character> the_characters;
public void OnLevelRequested(Session session, JObject message) {
string account_id = (string) message ["account_id"];
Character ch = null;
if (the_characters.TryGetValue (account_id, out ch))
{
return;
}
// 先确认这对象能否在本 Message Handler 访问后,不能访问的话,调用 Refresh()。
if (!ch.IsFresh ())
{
ch.Refresh (funapi.LockType.kWriteLock);
}
JObject response = new JObject ();
response ["level"] = ch.GetLevel ();
session.SendMessage ("level", response);
}
|
14.1.8. 使用JSON 对象进行初始化¶
为了对象初始化,可使用个别属性提供的 setter 但是,本方法是在有很多属性情况下很麻烦。 因此,iFun Engine 的 ORM 使用 JSON 产生一次对象初始化的方法。
Tip
通过本方法,可以简便地进行用户初始化或者新道具初始化的工作。
而且,从外部 JSON 抵用初始化的数据的话,策划人员只要修改 JSON 就可以变更默认值。这样的话工程师不用写死的工作。 对于管理策划数据的 JSON 文件方法, 请您参考 内容支持Part4: 策划数据 。
struct OpaqueData;
static Ptr<OpaqueData> CreateOpaqueDataFromJson(const Json &json);
bool PopulateFrom(const Ptr &opaque_data);
Note
将来会支持。
为了使用 JSON 进行初始化的时候需要如下两个阶段。
使用
CreateOpaqueDataFromJson(...)
参数,把 JSON 数据创建为该对象的 OpaqueData使用
PopulateFrom(opaque_data)
在对象输入创建的 OpaqueData
JSON跟着该对象模型的属性结构。 则,如 {"属性名称": "值"}
即可。
如果这时候属性不是基本类型的话,输入如下即可。
如果属性为 Array 的话,使用 JSON Array 输入其值。
如果属性为 Array 的话,使用 JSON Object 输入其值。
如果属性为其他模型的话,使用 JSON Object 输入其值。
Important
如果属性为其他模型和该模型有 Key 属性的话,不能使用这种方法。
各别对象的 Key 一定会固有。但是,通过 JSON 对象初始化的时候,相当于创建有同样 Key 值的对象。
例子: 按职业人物初始化
character_init_data.json
{
"Warrior": {
"Level": 1,
"Exp": 0,
"Hp": 1000,
"Mp": 100
},
"Wizard": {
"Level": 1,
"Exp": 0,
"Hp": 100,
"Mp": 1000
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | void OnCreateCharacter(const Ptr<Session> &session, const Json &message) {
// 假设以 message 的 character_name 形式发送人物的 Name。
string character_name = message["character_name"].GetString();
// 1. 首先创建人物 object。
Ptr<Character> ch = Character::Create(character_name);
if (ch) {
Ptr<const Json> json_data = ResourceManager::GetJsonData("character_init_data.json");
BOOST_ASSERT(json_data);
// 属于在 JSON 数据的战士人物进行初始化为 "Warrior" 。
// 如果是魔法师的话,把 "Warrior" 换成 "Wizard" 即可。
// 1. 使用 JSON 创建 OpaqueData 。
Ptr<Character::OpaqueData> opaque_data =
Character::CreateOpaqueDataFromJson((*json_data)["Warrior"]);
BOOST_ASSERT(opaque_data);
// 2. 在新创建的 Character Object 里输入 OpaqueData 后,进行初始化。
bool success = ch->PopulateFrom(opaque_data);
BOOST_ASSERT(success);
}
}
|
Note
将来会支持。
14.2. 数组 & 映射¶
我们已经解释了在 读取/写作对象属性 对各别属性产生 getter 和 setter 。 在基本类型的情况下,getter 就返回基本类型值,setter 也收到基本类型值。 但是,如果数组和映射类型的话,返回数组和映射类型的对象,通过该对象的方法,管理数组和映射。
14.2.1. 数组 : ArrayRef<Type>¶
我们还解释了使用 JSON 模型定义文件指定属性的时候,如 Type[ ]
添加 [ ]
就成为数组。
这样定义的数组的属性以 ArrayRef<Type>
形式产生。
比如说,String[]
成为 ArrayRef<string>
, Character[]
成为 ArrayRef< Ptr<Character> >
。
ArrayRef<T>
有如下组员。
14.2.1.2. 确认元素是否在特定位置¶
确认元素是否在特定位置。 index 是当[0, SIZE - 1]时有效。 如果 index 不可用的话,留 FATAL 日志后把服务器会关闭。
bool Has(size_t index) const
bool Has(ulong index)
14.2.1.3. 读取特定位置的元素¶
获得在数组 index 保存的 element 。 index 是当[0, SIZE - 1]时有效。
T GetAt(size_t index) const
T GetAt(ulong index)
14.2.1.4. 在特定位置写元素¶
在数组的 index 位置写值 index 是当[0, SIZE - 1]时有效。 如果 index 不可用的话,留 FATAL 日志后把服务器会关闭。
void SetAt(size_t index, const T &value)
void SetAt(ulong index, T value)
14.2.1.5. 在特定位置添加元素¶
在数组 添加 index 值。 结果增加数字的长度。 index 是当[0, SIZE - 1]时有效。 如果 index 不可用的话,留 FATAL 日志后把服务器会关闭。
Important
如果数组中间发生 InsertAt(...)
的话, 会重整数组,这时发生负荷。
void InsertAt(size_t index, const T &value)
void InsertAt(ulong index, T value)
14.2.1.6. 删除特定位置的元素¶
删除数组的 index 元素和减少数组。 index 是当[0, SIZE - 1]时有效。 如果 index 不可用的话,留 FATAL 日志后把服务器会关闭。
元素是其他 object 的时候,如果 delete_object
为 true
的话, 调用元素 object 的 Delete()
参数后一起删除。
Important
如果数组中间发生 EraseAt(...)
的话, 会重整数组,这时发生负荷。
void EraseAt(size_t index, bool delete_object = true)
void EraseAt(ulong index, bool delete_object = true)
14.2.1.7. 全数组空出¶
删除所有 element 后把数组的 SIZE 作为 0 。
元素是其他 object 的时候,如果 delete_object
为 true
的话, 调用元素 object 的 Delete()
参数后一起删除。
void Clear(bool delete_object = true);
void Clear(bool delete_object = true)
14.2.1.10. (方便功能)在数组首次添加元素¶
在首次添加元素。增加数组的大小。如 InsertAt(0, ...)
。
void PushFront(const T &value);
void PushFront(T value)
14.2.1.11. (方便功能)在数组最后添加元素¶
在最后添加元素。增加数组的大小。如 InsertAt(SIZE, ...)
。
void PushBack(const T &value);
void PushBack(T value)
14.2.1.12. (方便功能)在数组寻找首次空的插槽。¶
返回首次寻找 slot 的 index 。如果 slot 空的话, 返回 -1 。
Important
如果 Array 的 value 设置为如下资料性和默认值的时候,被认识空的 slot 。
资料性 |
默认值 |
---|---|
Bool |
false |
Integer |
0 |
Double |
0.0 |
String |
“” |
User-Defined Object |
null |
int64_t FindFirstEmptySlot() const;
long FindFirstEmptySlot()
14.2.2. Map: MapRef<KeyType, ValueType>¶
我们已解释了使用 JSON 模型定义文件指定属性类型的时候,指定如 <KeyType, ValueType>
成为 Map 形式。
Map 的话,在 Model 定义为 <KeyType, ValueType>
。 KeyType 会成为 Bool
, Integer
, Double
, String(n)
的基本类型。ValueType 除了这些外,还可以是对象类型(即另一个对象模型)。由此定义的映射属性以 MapRef<KeyType, ValueType>
的形式创建。
比如说,定于 <Integer, String>
的话, 是 MapRef<int64_t, string>
, 定义 <String, Character>
是 MapRef<string, Ptr<Character> >
。
MapRef<KeyType, ValueType>
有如下组员。
14.2.2.1. 确认在特定 key 是否有元素¶
确认在 Map 该 Key 是否存在。
bool Has(const KeyType &key) const
bool Has(KeyType key)
14.2.2.2. 读取在特定 key 的元素¶
Map 의 해당 key 에 저장된 element 를 얻습니다. 만약 key 가 유효하지 않으면 LOG 와 함께 Crash 합니다. 获取在 map 的该 Key 保存的 element。 如果 key 不可用的话, LOG也会冲突。
ValueType GetAt(const KeyType &key) const
ValueType GetAt(KeyType key)
14.2.2.3. 在特定 key 插入元素¶
在 Map 上变更该 key 值。 如果其 key 没有 element 的话,要添加。
void SetAt(const KeyType &key, const ValueType &value)
void SetAt(KeyType key, ValueType value)
14.2.2.4. 특정 키의 원소 삭제 删除特定 key 的元素¶
在 Map 上删除该 key 。 value 是对象形式的时候,delete_object
是 true
的话, 调用该 Object 的 Delete()
参数后删除。 如果 key 不存在的话,会返回 false 。
bool EraseAt(const KeyType &key, bool delete_object = true)
bool EraseAt(KeyType key, bool delete_object = true)
14.2.2.5. 清空全 MAP¶
删除全 element,把 Map 的 SIZE 作为 0 。
value 是对象形式的时候, delete_object
是 true
的话, 调用该 Object 的 Delete()
参数后删除
void Clear(bool delete_object = true)
void Clear(bool delete_object = true)
14.2.2.6. 返回全 Key 值¶
返回在 Map 上的所有的 Key。
Tip
读取全 Key 的工作负荷会很重。 因此,我们建议每逢使用调用参数不如保存已经调用的值后再使用。
std::vector<KeyType> Keys() const
SortedSet<TKey> Keys
14.2.2.7. 사이즈 반환¶
Map 저장된 Element 의 수를 반환합니다.
size_t Size() const
UInt64 Length
Note
1.0.0-2885 Experimental
이상 버전에서만 사용할 수 있습니다.
14.2.2.8. (方便功能)在整数 Map 上寻找首次空的插槽¶
Important
这是 KeyType 为 Integer
的时候,才可以用。
从0起增加每1个增加。 如果不包括 map 上的 key 的话,就返回其 key 值。
int64_t FindFirstEmptySlot() const
long FindFirstEmptySlot()
14.3. 接口类使用例子¶
14.3.1. 仓库初始化¶
下面的代码创建 Character 和 10个间的 Inventory。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void CreateCharacter() {
Ptr<Character> ch = Character::Create("legend");
if (not ch) {
return;
}
ArrayRef<Ptr<Item> > inventory = ch->GetInventory();
// 在 Inventory 创建10个间的空插槽。
for (size_t i = 0; i < 10; ++i) {
// Item::kNullPtr 是 NULL 值为 Ptr<Item> 的常数。
inventory.PushBack(Item::kNullPtr);
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public void CreateCharacter()
{
Character ch = Character.Create ("legend");
if (ch == null)
{
return;
}
ArrayRef<Item> inventory = ch.GetInventory ();
// 在 Inventory 创建10个间的空插槽。
for (int i = 0; i < 10; ++i)
{
inventory.PushBack(null);
}
}
|
14.3.2. 在 Inventory 发送道具¶
下面的代码调用 Character 后在 Inventory 的 3号插槽创建新道具和发送。
1 2 3 4 5 6 7 8 9 10 11 | void AddItem() {
Ptr<Character> ch = Character::FetchByName("legend");
if (not ch) {
return;
}
ArrayRef<Ptr<Item> > inventory = ch->GetInventory();
Ptr<Item> item = Item::Create();
inventory.SetAt(3, item);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 | public void AddItem()
{
Character ch = Character.FetchByName("legend");
if (ch == null)
{
return;
}
ArrayRef<Item> inventory = ch.GetInventory();
Item item = Item.Create();
inventory.SetAt(3, item);
}
|
14.3.3. 在仓库中删除道具¶
下面的代码调用 Character 后删除在 Inventory 中的7号插槽和消除道具。 如果想把道具移动到其他插槽或者其他人物的话,要扣删除的代码。
Important
在仓库里删除道具不是使用 EraseAt(index)
而是使用 SetAt(index, null)
。
前者是把数组长度删除1,因此相当于删除仓库的插槽。
所以在下面例子表示在7号插槽里使用 SetAt(index, null)
设置 null。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void DeleteItem() {
Ptr<Character> ch = Character::FetchByName("legend");
if (not ch) {
return;
}
ArrayRef<Ptr<Item> > inventory = ch->GetInventory();
Ptr<Item> item = inventory.GetAt(7);
if (item) {
inventory.SetAt(7, Item::kNullPtr);
item->Delete();
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public void DeleteItem()
{
Character ch = Character.FetchByName ("legend");
if (ch == null)
{
return;
}
ArrayRef<Item> inventory = ch.GetInventory ();
Item item = inventory.GetAt (7);
if (item != null)
{
inventory.SetAt (7, null);
item.Delete ();
}
}
|
14.3.4. 空的仓库插槽和发送道具¶
下面的代码调用 Character 在仓库中首次找到空的插槽里发送道具。 如果没有空的插槽的话, 增加仓库后最后仓库里发送道具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void AddItem() {
Ptr<Character> ch = Character::FetchByName("legend");
if (not ch) {
return;
}
Ptr<Item> item = Item::Create();
ArrayRef<Ptr<Item> > inventory = ch->GetInventory();
int64_t empty_slot_index = inventory.FindFirstEmptySlot();
if (empty_slot_index == -1) {
inventory.PushBack(item);
} else {
inventory.SetAt(empty_slot_index, item);
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public void AddItem()
{
Character ch = Character.FetchByName ("legend");
if (ch == null)
{
return;
}
Item item = Item.Create();
ArrayRef<Item> inventory = ch.GetInventory();
long empty_slot_index = inventory.FindFirstEmptySlot();
if (empty_slot_index == -1) {
inventory.PushBack(item);
}
else
{
inventory.SetAt(empty_slot_index, item);
}
}
|
14.3.5. 创建装备道具的插槽¶
下面代码先创建 Character 然后再创建在头,胸,右手,左手装备道具的插槽。
1 2 3 4 5 6 7 8 9 10 11 12 13 | void CreateCharacter() {
Ptr<Character> ch = Character::Create("legend");
if (not ch) {
return;
}
MapRef<string, Ptr<Item> > equips = ch->GetEquippedItem();
equips.SetAt("head", Item::kNullPtr);
equips.SetAt("chest", Item::kNullPtr);
equips.SetAt("righthand", Item::kNullPtr);
equips.SetAt("lefthand", Item::kNullPtr);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void CreateCharacter()
{
Character ch = Character.Create ("legend");
if (ch == null)
{
return;
}
MapRef<string, Item> equips = ch.GetEquippedItem();
equips.SetAt ("head", null);
equips.SetAt ("chest", null);
equips.SetAt ("righthand", null);
equips.SetAt ("lefthand", null);
}
|
14.3.6. 装备道具¶
下面代码调用 Character 把在 Inventory 里5号插槽的道具装备到右手。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void EquipWithItem() {
Ptr<Character> ch = Character::FetchByName("legend");
if (not ch) {
return;
}
ArrayRef<Ptr<Item> > inventory = ch->GetInventory();
MapRef<string, Ptr<Item> > equips = ch->GetEquippedItem();
if (inventory.Has(5)) {
Ptr<Item> item = inventory.GetAt(5);
inventory.SetAt(5, Item::kNullPtr);
equips.SetAt("righthand", item);
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public void EquipWithItem()
{
Character ch = Character.FetchByName ("legend");
if (ch == null)
{
return;
}
ArrayRef<Item> inventory = ch.GetInventory ();
MapRef<string, Item> equips = ch.GetEquippedItem ();
if (inventory.Has (5))
{
Item item = inventory.GetAt (5);
inventory.SetAt (5, null);
equips.SetAt ("righthand", item);
}
}
|
14.3.7. 在空的快槽里装备道具¶
下面代码创建 Character 后在 QuickSlot 上找到的首次空的插槽里装备道具。
1 2 3 4 5 6 7 8 9 10 11 12 13 | void CreateCharacter() {
Ptr<Character> ch = Character::Create("legend");
if (not ch) {
return;
}
Ptr<Item> item = Item::Create();
MapRef<int64_t, Ptr<Item> > quick_slot = ch->GetQuickSlot();
int64_t empty_slot_index = quick_slot.FindFirstEmptySlot();
quick_slot.SetAt(empty_slot_index, item);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public void CreateCharacter()
{
Character ch = Character.Create ("legend");
if (ch == null)
{
return;
}
Item item = Item.Create();
MapRef<ulong, Item > quick_slot = ch.GetQuickSlot ();
long empty_slot_index = quick_slot.FindFirstEmptySlot();
quick_slot.SetAt(empty_slot_index, item);
}
|
14.3.8. 创建人物¶
下面是收到创建 Character 数据包后,产生 Character Object 的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | void OnCreateCharacter(const Ptr<Session> &session, const Json &message) {
string character_name = message["character_name"].GetString();
LOG(INFO) << "chracter name : " << character_name;
Ptr<Character> ch = Character::Create(character_name);
Json response;
if (ch) {
// 人物创建完了。
// 输入基本人物信息后,在仓库添加新手用刀。
response["result"] = true;
ch->SetLevel(1);
ch->SetHp(100);
ch->SetMp(100);
ch->SetExp(0);
Ptr<Item> novice_sword = Item::Create();
Ptr<Item> hp_potion = Item::Create();
// 在 Inventory 上首次插槽里发送新手用刀。
ch->GetInventory().InsertAt(0, novice_sword);
// 把新手用装备到 "右手" 。
ch->GetEquippedItem().SetAt("RightHand", novice_sword);
// 注意) 在 Inventory 和 EquippedItem 里各个输入 novice_sword
// 这两个意味着同样的对象。(不是复制)
// 在快槽首次曹操里添加 hp 药水。
ch->GetQuickSlot().SetAt(0, hp_potion);
} else {
// 因已经有同样名称的人物而没有创建。
response["result"] = false;
response["reason"] = character_name + " already exists";
}
session->SendMessage("create_character", response);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public void OnCreateCharacter(Session session, JObject message) {
string character_name = (string) message["character_name"];
Log.Info("chracter name = {0}", character_name);
Character ch = Chracter.Create(character_name);
JObject response = new JObject ();
if (ch != null) {
// 创建人物
// 设置任务,在仓库添加新手用刀。
response ["result"] = true;
ch.SetLevel (1);
ch.SetHp (100);
ch.SetMp (100);
ch.SetExp (0);
Item novice_sword = Item.Create ();
Item hp_potion = Item.Create ();
// 在 Inventory 上首次插槽里发送新手用刀。
ch.GetInventory().InsertAt (0, novice_sword);
// 把新手用装备到 "右手" 。
ch.GetEquippedItem().SetAt ("RightHand", novice_sword);
// 注意) 在 Inventory 和 EquippedItem 里各个输入 novice_sword
// 这两个意味着同样的对象。(不是复制)
// 在快槽首次曹操里添加 hp 药水。
ch.GetQuickSlot().SetAt (0, hp_potion);
}
else
{
// 因已经有同样名称的人物而没有创建。
response ["result"] = false;
response ["reason"] = character_name + " already exists";
}
session.SendMessage ("create_character", response);
}
|
14.3.9. 增加人物经验值和级别¶
人物的经验值增加 100 ,如果经验值超过 3000 的话,会升级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void OnKill(const Ptr<Session> &session, const Json &msg) {
string id = msg["UserId"].GetString();
string ch_name = msg["CharacterName"].GetString();
Ptr<User> user = User::FetchById(id);
if (not user) {
return;
}
Ptr<Character> ch = user->GetMyCharacter();
if (not ch) {
return;
}
ch->SetExp(ch->GetExp() + 100);
if (ch->GetExp() >= 3000) {
ch->SetLevel(ch->GetLevel() + 1);
ch->SetExp(ch->GetExp() - 3000);
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public void OnKill(Session session, JObject message)
{
string id = (string) message ["UserId"];
string ch_name = (string) message ["CharacterName"];
User user = User.FetchById (id);
if (user == null)
{
return;
}
Character ch = user.GetMyCharacter ();
if (ch == null)
{
return;
}
ch.SetExp (ch.GetExp () + 100);
if (ch.GetExp () >= 3000)
{
ch.SetLevel (ch.GetLevel () + 1);
ch.SetExp (ch.GetExp () - 3000);
}
}
|