Jump to content

Josh

Staff
  • Posts

    24,625
  • Joined

  • Last visited

Everything posted by Josh

  1. Take a look at this. Instead of a host/peer design, this is a more intuitive server/client design. If you are hosting a game, you create a server. If you want to play in the game yourself, create a client. Here, I created two clients. You could create a client for each bot and run them through the network, or just one client for yourself. You'd probably want to always run the game through the network, even if it is single-player, so that you have a uniform design for single and multi player modes. The messages are turned into a form with an ID and a packet, which is a stream of any length. There are built-in message IDs. Some are handled entirely silently. For example, when the server receives a chat message, it just broadcasts the data to all clients, without the user having to do anything. Synced entities would behave this way as well. The user's interaction with the system comes in the form of a callback. The callback can be used to do stuff when messages are received. In the example below, the client callback is used to print the ping and to print any chat messages that are sent. SuperStrict Private Function enet_host_port:Int( peer:Byte Ptr ) Local ip:Int=(Int Ptr peer)[1] Local port:Int=(Short Ptr peer)[4] ?LittleEndian ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24) ? Return port EndFunction Function enet_host_ip:Int( peer:Byte Ptr ) Local ip:Int=(Int Ptr peer)[1] Local port:Int=(Short Ptr peer)[4] ?LittleEndian ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24) ? Return ip EndFunction Function enet_peer_port:Int( peer:Byte Ptr ) Local ip:Int=(Int Ptr peer)[3] Local port:Int=(Short Ptr peer)[8] ?LittleEndian ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24) ? Return port EndFunction Function enet_peer_ip:Int( peer:Byte Ptr ) Local ip:Int=(Int Ptr peer)[3] Local port:Int=(Short Ptr peer)[8] ?LittleEndian ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24) ? Return ip EndFunction Type TNetworkNode Field port:Int Field ip:Int Field enethost:Byte Ptr Field enetpeer:Byte Ptr Method Delete() If enethost enet_host_destroy(enethost) enethost=Null EndIf EndMethod Method Update(callback(client:TClient,id:Int,packet:TPacket)=Null) Local ip:Int,port:Int,client:TClient,ev:ENetEvent=New ENetEvent,id:Byte,packet:TPacket If Not Self.enethost RuntimeError "Can't update a remote server." Repeat If enet_host_service(Self.enethost,ev,0) Select ev.event Case ENET_EVENT_TYPE_CONNECT id=NETWORK_CONNECT Case ENET_EVENT_TYPE_CONNECT id=NETWORK_DISCONNECT Case ENET_EVENT_TYPE_RECEIVE Local size:Int=enet_packet_size(ev.packet) Local data:Byte[size] MemCopy(Varptr id,enet_packet_data(ev.packet),1) If size>1 packet=New TPacket packet._bank.resize(size-1) MemCopy(packet._bank.buf(),enet_packet_data(ev.packet)+1,size-1) EndIf Default Continue EndSelect EvaluateEvent(id,packet,ev.peer) Else Exit EndIf Forever EndMethod Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr) Abstract EndType Public Const NETWORK_CONNECT:Int=1 Const NETWORK_DISCONNECT:Int=2 Const NETWORK_PINGREQUEST:Int=3 Const NETWORK_PINGRESPONSE:Int=4 Const NETWORK_JOINREQUEST:Int=5 Const NETWORK_JOINREPLY:Int=6 Const NETWORK_CHAT:Int=7 Type TServer Extends TNetworkNode Const maxplayers:Int=64 Field clients:TList=New TList Field callback(server:TServer,id:Int,packet:TPacket) Method Delete() If enetpeer enetpeer=Null EndIf If enethost enet_host_destroy(enethost) enethost=Null EndIf EndMethod Function Create:TServer(ip:Int=0,port:Int=7777) Local server:TServer=New TServer,addr:Byte Ptr If ip server.ip=ip Else server.ip=ENET_HOST_ANY EndIf server.port=port If server.port<>0 Or server.ip<>ENET_HOST_ANY addr=enet_address_create(server.ip,server.port) EndIf server.enethost=enet_host_create(addr,maxplayers,0,0) If addr enet_address_destroy(addr) EndIf If Not server.enethost Return Null EndIf Return server EndFunction Method FindClient:TClient(ip:Int,port:Int) Local client:TClient For client=EachIn clients If client.ip=ip And client.port=port Return client Next EndMethod Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr) Local client:TClient Local issilent:Int=False client=FindClient(enet_peer_ip(enetpeer),enet_peer_port(enetpeer)) Select id Case NETWORK_CONNECT If client Disconnect(client,True) client=New TClient client.enetpeer=enetpeer client.ip=enet_peer_ip(enetpeer) client.port=enet_peer_port(enetpeer) client.link=clients.AddLast(client) Case NETWORK_PINGREQUEST Send(client,NETWORK_PINGRESPONSE,packet) issilent=True Case NETWORK_CHAT Broadcast(id,packet) issilent=True EndSelect If Not issilent If callback callback(Self,id,packet) EndIf EndIf EndMethod Method Send:Int(client:TClient,id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0) Local enetpacket:Byte Ptr Local result:Int If Not client.enetpeer RuntimeError "Can't send to local client." Local data:Byte[] If packet If packet._bank.size()=0 packet=Null EndIf If packet data=New Byte[packet._bank.size()+1] MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size()) Else data=New Byte[1] EndIf data[0]=id enetpacket=enet_packet_create(data,data.length,flags) result=(enet_peer_send(client.enetpeer,channel,enetpacket)=0) Return result EndMethod Method Broadcast(id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0) Local enetpacket:Byte Ptr Local result:Int Local data:Byte[] If packet If packet._bank.size()=0 packet=Null EndIf If packet data=New Byte[packet._bank.size()+1] MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size()) Else data=New Byte[1] EndIf data[0]=id enetpacket=enet_packet_create(data,data.length,flags) enet_host_broadcast(Self.enethost,channel,enetpacket) EndMethod Method Disconnect(client:TClient,force:Int=False) If client.enetpeer If force enet_peer_reset(client.enetpeer) Else enet_peer_disconnect(client.enetpeer) EndIf client.link.remove() EndIf EndMethod EndType Type TClient Extends TNetworkNode Const maxplayers:Int=64 Field link:TLink Field server:TServer Field connected:Int Field callback(client:TClient,id:Int,packet:TPacket) Function Create:TClient(ip:Int=0,port:Int=7776) Local client:TClient=New TClient Local addr:Byte Ptr If ip client.ip=ip Else client.ip=ENET_HOST_ANY EndIf client.port=port If client.port<>0 Or client.ip<>ENET_HOST_ANY addr=enet_address_create(client.ip,client.port) client.enethost=enet_host_create(addr,maxplayers,0,0) If addr enet_address_destroy(addr) If Not client.enethost Return Null EndIf Return client EndFunction Method Disconnect(force:Int=False) If server If force enet_peer_reset(server.enetpeer) Else enet_peer_disconnect(server.enetpeer) EndIf server=Null EndIf EndMethod Method Connect:TServer(ip:Int,port:Int) Local addr:Byte Ptr,server:TServer If Not Self.enethost RuntimeError "Remote client cannot connect to server." If server Disconnect() server=New TServer server.ip=ip server.port=port addr=enet_address_create(server.ip,server.port) server.enetpeer=enet_host_connect(Self.enethost,addr,1) enet_address_destroy(addr) If server.enetpeer=Null Return Null Self.server=server Return server EndMethod Method Send:Int(id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0) Local enetpacket:Byte Ptr Local result:Int If Not connected Return 0 If Not server RuntimeError "Client is not connected." Local data:Byte[] If packet If packet._bank.size()=0 packet=Null EndIf If packet data=New Byte[packet._bank.size()+1] MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size()) Else data=New Byte[1] EndIf data[0]=id enetpacket=enet_packet_create(data,data.length,flags) result=(enet_peer_send(server.enetpeer,channel,enetpacket)=0) Return result EndMethod Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr) Local issilent:Int=False Select id Case NETWORK_CONNECT Self.connected=True Case NETWORK_PINGRESPONSE Case NETWORK_CHAT EndSelect If Not issilent If callback callback(Self,id,packet) EndIf EndIf EndMethod Method Ping:Int() Local packet:TPacket=New TPacket packet.WriteInt(MilliSecs()) Return Send(NETWORK_PINGREQUEST,packet) EndMethod Method Say:Int(text:String) Local packet:TPacket=New TPacket packet.WriteLine(text) Return Send(NETWORK_CHAT,packet) EndMethod EndType Type TPacket Extends TBankStream Method New() _bank=New TBank EndMethod EndType Function CreatePacket:TPacket() Local packet:TPacket=New TPacket Return packet EndFunction '---------------------------------------------------------------------------------------------------- Local server:TServer Local client1:TClient Local client2:TClient Local ip:Int=HostIp("127.0.0.1") server=TServer.Create(ip,7777) client1=TClient.Create(ip,7776) client2=TClient.Create(ip,7779) client1.connect(server.ip,server.port) client2.connect(server.ip,server.port) client1.callback=ClientCallBack client2.callback=ClientCallBack server.callback=ServerCallBack Repeat server.Update() client1.Update() client2.Update() Delay 1 If Rand(100)=1 client1.Ping() client2.Say("Hello!") EndIf Forever Function ServerCallBack(server:TServer,id:Int,packet:TPacket) Select id Case NETWORK_CONNECT Print "New client connected." EndSelect EndFunction Function ClientCallBack(client:TClient,id:Int,packet:TPacket) Select id Case NETWORK_CONNECT Print "Connected to server." Case NETWORK_CHAT Print "Says: "+packet.ReadLine() Case NETWORK_PINGRESPONSE Print "Ping = "+(MilliSecs()-packet.ReadInt()) EndSelect EndFunction
  2. It depends mostly on what your criteria is. If you want good graphics and good physics, this engine is your best bet.
  3. Use uncompressed DDS format. DXTC1 is the worst format you can use. It should never be used. Use DXTC5 for compressed textures.
  4. I can't view your animation right now because I do not have 3ds max. If you are not an expert at animating, I would not put any more time into it. Thanks for trying to help, though.
  5. Josh

    Next up...

    The foot placement seen there can be achieved using physics. Inverse Kinematics is physic joints. The two are the same thing. First thing is I need to get the soldier model animated.
  6. How do you want the coordinates? It can be as simple as writing a short script that prints the camera position.
  7. This is amazing. You even used the average pixel color to control the light color.
  8. I am asking because I don't want to waste his time. I also don't expect this to be free.
  9. Josh

    buffer access

    Because C++ programmers want to be able to call framework commands with Lua.
  10. Do you have a portfolio? What kind of animation work have you done?
  11. Make a mesh out of n*2 number of triangles, and move the vertices around to make particles. That's how we all did it in the days of Blitz3D.
  12. Josh

    weather system

    1. Move the emitter around with the camera. 2. Render the scene to a depth-only buffer with an orthogonal camera over the player's head looking down. Then use this depth buffer in the particle fragment shader to discard fragments that are behind what the depth buffer sees. This is how STALKER handles rain. Not easy, but if you look at some of the shadow shaders everything you need is there. I have been meaning to do this, but just haven't had time.
  13. Josh

    Next up...

    There sure is a lot to do around here! -Character animation example. I want to get the soldier model up and running with basic animations so everyone can use it. -Network improvements. I want to automate entity movement and some other features. -Scene graph improvements and vegetation collision. -Website and forum integration. -Revamping some of the tutorials. Watch the character movement in this video. I want to set up something simple like that so we can run around and have fun: Also need to get a new public demo out, but I won't release one until ATI fixes their cubemap bug! Apparently, the report has gone to the highest levels of AMD, and it is in the process of being fixed.
  14. The question really is how many polygons can your GPU render fluently? A 3D engine itself does not make much of a difference, as long as it is using the fast-track rendering pathways. A few hundred thousand polys (plus shadow polys) is usually comfortable. You can easily render tens of thousands of trees and plants. I don't understand this. I think so. Look at the user gallery. It's pretty easy for a programmer to download a few good-looking models and put together something that looks really good. I don't understand this. BlitzMax will be fine if you are already comfortable with that.
  15. You could do that just by rearranging the vertices of a mesh.
  16. Josh

    Scenegraph Stuff

    You won't see any difference in a small or medium size project, but it will allow very large scenes with a large number of objects. It will also allow hundreds of AI actors, as long as they aren't all grouped together.
  17. I am looking for someone to add some animations to the soldier character. I need the following cycles: -idle -run -run left -run right -run backwards -turn feet left -turn feet right The character should be posed to hold a rifle-type gun in each animation.
  18. Yes, you can set the occlusion culling mode to 0 (disabled). I believe this problem has been fixed pretty well in more recent versions of the engine.
  19. You can't really move a particle on its own, either. Particles are never removed, they just get recycled in a loop.
  20. You can't delete a single particle.
  21. Discussing scene graphs in a game development or programming forum can be difficult, because I feel many people are very short-sighted about all the exceptions and complications that can arise in a flexible environment. In fact, there are many situations that will make rendering with a scene graph much slower than without. Many programmers have a tendency to ignore anticipated problems and assume it will work itself out somehow. It's also very hard to predict how bottlenecks will form and what "typical" behavior in a game will turn out to be. Thus, in the past I have never gotten much out of all the discussions and theories that exist on this subject. The primary complication arises from the fact that some objects are typically static, while others may move around. Static objects are infrequently added to or removed from the scene graph, so quad trees or octrees work great. Dynamic objects do not work well with these approaches because of the amount of data that has to be recalculated. Another problem appears when an object crosses over the bounds of two "cells" or areas. If an object lies on an edge where it overlaps two areas, what should you do? Recalculating the bounding box of the area is one solution. Consider a quad tree with three branches. If a car in one leaf moves, the branches two levels up have to have their AABB recalculated. If the leaf contains a large number of objects, it may require iterating through all of them to recalculate the AABB. So this solution isn't very good for a scene with a lot of moving objects. This approach, incidentally, is the one used internally by the vegetation system. Another approach is to list the object in both areas. However, if you have a large building that may occupy dozens of separate areas at once, this approach can create a lot of unnecessary overhead. Before I discuss my solution, let's consider the fundamental differences between static and dynamic objects in a scene. Static objects are usually used for trees, grass, plants, buildings, fences, and other repetitive objects. They tend to be very high in density and quantity. Dynamics objects have an overall tendency to be more spread out and fewer. An area might be littered with a lot of physically interactive debris, but that's still less than the tens or even hundreds of thousands of trees that might exist. This means the two kinds of objects have fundamental differences in distribution that makes different scenegraph systems more optimal for either. For static objects, the quad tree approach with adjusting bounding boxes is fantastic. For dynamic objects, it makes more sense in practice to have a nonrecursive grid with adjusting bounding boxes. It's pointless to have recursive levels of bounding box adjustment and culling when the quantity of dynamic objects is nowhere near that of the vegetation system, which benefits from a heavy optimization system. The renderer can just grab a square of sectors within the camera range, in the same way the terrain renderer does, as this pseudo code shows: for x=camera.position.x - camera.range to camera.position.x + camera.range { for z=camera.position.z - camera.range to camera.position.z + camera.range { if !sector:Culled(camera) { sector[x,z]:Draw(camera) } } } This also is optimal for AI and other updating. The S.T.A.L.K.E.R. engine has a setting for AI range. This is how they are able to have so many characters in the world, and only have to worry about updating the ones in the near vicinity. I might add an "update range" setting so that only models within a certain distance have their Update callbacks and script functions run. The code to do this would look something like this: for x=camera.position.x - camera.range to camera.position.x + camera.range { for z=camera.position.z - camera.range to camera.position.z + camera.range { sector[x,z]:Update() } } These are some small improvements I plan to make. The effects won't be immediately apparent to most people, but they will ensure your project will stay fast as your work becomes more advanced.
  22. I'm sure you could use a couple of static bodies for the feet, a static body for the pelvis, and a body with mass for the knees. Connect the knees to the pelvis with a ball joint with limits, and the feet to the knees with a hinge. Move the feet wherever you want them to be, and the knees will follow. Parent the knee bones to the knee bodies, and you have IK animation.
  23. It is possible to render to two separate windows, but you will need to initialize an OpenGL context in each yourself.
  24. The gray skin has some problems with the gallery and blogs. It's removed.
×
×
  • Create New...