英文:
Trouble figuring out go object composition
问题
我目前正在使用Go语言实现一个游戏服务器,因为它拥有强大的并发原语。这一切都很容易实现,并且非常可靠。不过,我主要是从Java背景过来的,我很难忘记Java的类型层次结构。我需要一个包含所有游戏对象的集合,它们都共享类似的数据结构,比如位置、速度等,但它们的更新方法可能有不同的行为,或者有特殊的字段以实现不同的功能。最初,我使用一个包含所有共享数据的结构体,然后每个自定义类型都会嵌入该结构体。问题在于如何使用数据结构进行组织。我使用四叉树来通知客户端附近对象的状态。这样,我可以将每个游戏对象的嵌入结构体的指针传递给四叉树,一切都正常工作。
但问题是,一旦查询四叉树,我就无法访问包含的类型。例如,
type GameObject struct {
Position Point
Velocity Point
ID int32
}
type Client struct {
GameObject
conn net.Conn
// 其他字段
}
现在,如果我想更新附近的玩家关于一个对象的状态,我会查询四叉树,但无法确定GameObject实际上是一个Client,并且无法访问它的连接以发送数据包。
从Java的角度来看,我习惯于创建一个基类,并对其进行子类化,比如Client等。然后,我可以使用instanceof
来确定哪些对象需要特殊处理,并相应地进行类型转换以访问自定义功能。我认为我可以做类似以下的事情:
type GameObject interface {
Position() Point
Velocity() Point
ID() int32
}
type Client struct {
pos Point
vel Point
id int32
conn net.Conn
// 其他字段
}
func (c *Client) Position() Point {
return c.pos
}
func (c *Client) Velocity() Point {
return c.vel
}
func (c *Client) ID() int32 {
return c.id
}
使用这种方法,我可以使用类型断言来隔离客户端,但这将导致大量重复代码来实现其他游戏对象。我想知道在Go语言中是否有更符合惯例的方法来实现类似的功能,我非常感谢任何帮助。如果这是Go语言的方式,也许有人可以帮我理解这种设计背后的原因。
英文:
I'm currently using go to implement a Game server due to the awesome concurrency primitives. That has all been a breeze to implement and works very reliably. I come mostly from a Java background though and I am having trouble forgetting Java's type hierarchy. I need to have a collection of game objects which all share a similar data structure like position, velocity, etc., but they may all have different behavior within their update methods, or special fields to allow different functionality. Initially I had a struct containing all of the shared data, and each custom type would embed that struct. The trouble is using data structures for organization. I use a quadtree to keep clients notified about nearby object's state. This way I can pass the tree a pointer to the embedded struct of each game object and everything works.
The problem with this is I can't access the containing type once I query the quadtree. So for example,
type GameObject struct {
Position Point
Velocity Point
ID int32
}
type Client struct {
GameObject
conn net.Conn
// other fields
}
Now, when If I want to update nearby players about an object's state, I query the quadtree, but there is now way to determine if the GameObject is actually a Client, and there is no way to access it's connection to send packets.
From Java, I'm used to creating a base class and subclassing it for Client, etc. Then I can use instanceof
to determine which ones need special treatment and cast them accordingly to access custom functionality. I think I could do something like the following:
type GameObject interface {
Position() Point
Velocity() Point
ID() int32
}
type Client struct {
pos Point
vel Point
id int32
conn net.Conn
// other fields
}
func (c *Client) Position() Point {
return c.pos
}
func (c *Client) Velocity() Point {
return c.vel
}
func (c *Client) ID() int32 {
return c.id
}
With this method I can use a type assertion to isolate the clients, but this will lead to a lot of duplicate code for implementing other game objects. I assume there is a more idiomatic way to do something like this in go, and I appreciate any help I can get. If this is the go way, maybe someone can help me understand the reasoning behind this design.
答案1
得分: 0
很难在不了解问题和约束条件的情况下回答这个问题,但是这里有三个想法。
首先,也许你可以在四叉树中存储一个复合类型,而不仅仅是 GameObject 指针。
type QuadTreeEntry struct {
GameOb *GameObject
Entity interface{}
}
"Entity" 可以是 *Client,以及其他你在四叉树中需要的内容。
其次,你可以考虑是否需要在四叉树中使用完整的 GameObject。也许这样会更好?
type QuadTreeEntry struct {
Position Point
Entity interface{}
}
这样做的好处是,在 QuadTree 代码中减少了获取 Position 的间接性,并且很难意外使 QuadTree 无效,因为它不与原始实体共享 Position 数据。
第三,如果效率不是太重要,你可以这样做:
type GameObjectGetter interface {
GetGameObject() *GameObject
}
func (c *Client) GetGameObject() *GameObject {
return &c.GameObject
}
这样,放入四叉树的所有内容都将是 GameObjectGetter。
英文:
It's hard to answer the question without knowing more about the problem and your constraints, but here's three ideas.
First, perhaps you can store a composite type in the quad tree rather than just the GameObject pointer.
type QuadTreeEntry struct {
GameOb *GameObject
Entity interface{}
}
The "Entity" would be a *Client, and whatever else goes in your quadtree.
Second, you might consider if you need all of GameObject in the quadtree. Perhaps this would be better?
type QuadTreeEntry struct {
Position Point
Entity interface{}
}
This has the nice effect of removing an indirection to get at the Position in the QuadTree code, and it's harder to accidentally invalidate your QuadTree, since its not sharing Position data with the original entity.
Third, if efficiency isn't too important, you could do this:
type GameObjectGetter interface {
GetGameObject() *GameObject
}
func (c *Client) GetGameObject() *GameObject {
return &c.GameObject
}
So everything that goes in the quad tree would be a GameObjectGetter.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论