PDA

View Full Version : Periphery Online / MMORTS



rts111
23-11-2021, 09:34 AM
Hi
Try my mmorts game - Periphery Online.
The client is written in Delphi, the server in Lazarus.

Short description:
A refugee capsule lands on an uninhabited planet.
You have to survive, rebuild civilization and, if possible, regain control over the Earth.

One big universe, one endless session for all players.

I'm preparing a publication on Steam, so the test version is not available right now.
https://store.steampowered.com/app/1873750/Periphery/



https://youtu.be/KYbRWSpmM5k


http://flora3d.net/periphery/help/p002.jpg http://flora3d.net/periphery/help/p004.jpg http://flora3d.net/periphery/help/p043.jpg ​http://flora3d.net/periphery/help/p025.jpg

http://flora3d.net/periphery/help/p027.jpg http://flora3d.net/periphery/help/p163.jpg http://flora3d.net/periphery/help/p165.jpg ​http://flora3d.net/periphery/help/p019.jpg


Screenshots:

http://flora3d.net/periphery/pic/new7.jpg

http://flora3d.net/periphery/pic/new5.jpg

http://flora3d.net/periphery/pic/new4.jpg

http://flora3d.net/periphery/pic/p3.jpg

Interface:
http://flora3d.net/periphery/pic/ui_en.jpg

JernejL
26-11-2021, 07:39 AM
do tell more on how you built the game, what graphics library do you use?

rts111
26-11-2021, 02:07 PM
Graphic API: OpenGL, GLSL.
Sound: DirectSound.

All plants and houses are made with the Flora3D tree generator.
For those who use this program, in the folder "../Periphery/Data/F3d" you can find additional collections of trees and bushes that you can freely use in your projects.

Models and animations from Mixamo.
First I import them into Blender, then export them in my own format.

No other libraries are used, game engine is self-written.

The server is also self-written, Linux/Posix, only native low-level functions: listen, send, recv, etc., no other lib.

SilverWarior
26-11-2021, 04:24 PM
Looks very interesting.

rts111
27-11-2021, 12:55 PM
Looks very interesting.

Thanks.

Landscape screenshots:

http://flora3d.net/periphery/pic/old1.jpg

http://flora3d.net/periphery/pic/old3.jpg

JernejL
08-12-2021, 09:03 AM
Could you do a writeup on how you did model animations? this is something i still have a problem with in my own game.

rts111
09-12-2021, 03:27 PM
Unfortunately, I don't know an easy way.

My path was like this:
- Learn how skeletal animation works in general.
- Learn Blender, and learn how to import models and animations from Mixamo.
- Learn Python, and learn how to write Python scripts in Blender.
- Write a script that exports these model and animation to my own format.
- In the game itself, you need to write functions for loading this format, for converting to format understandable GPU ...

If you're a beginner, all of this can take over a year. I think my answer will not help you much.

JernejL
10-12-2021, 08:22 AM
I know the principles behind the skinning but the issue is loading up the data in a consistient and useful way in pascal and getting it to opengl & shaders (or a decent CPU method to deform mesh).
There is no useful example that works with modern file formats in pascal, there are ancient quake file format examples in glscene and some GLTF examples which are super hard to integrate because they rely on certain way their shaders are done and are hard to just integrate into a custom engine pipeline, and most of these don't even have support to share animations between different meshes, if you want multiple characters you usually need to export animations for each model separatedly.

If you could make a minimal example of this, all of pascal community could benefit from it.

rts111
11-12-2021, 03:23 PM
The animation is too deeply integrated with the game engine. The code is written for internal useage only, it contains many strange and specific structures and names, it will not be understandable to the general public without detailed comments.

Right now It's too hard and not realistic to quickly make a simple animation example without the game engine itself. Sorry.


If anyone is interested, here is the Python code I use to export an animation model from Blender to my own binary format:


import os
import bpy
import mathutils
from struct import pack
from mathutils import Matrix, Vector, Color
from bpy_extras import io_utils, node_shader_utils



print('******************************************* **************')

oo = bpy.context.scene.objects



VV = []
TT = []
NN = []
WW = []

II = []
MM = []
GG = []

BB = []
KK = []

ML = []
MI = []
MB = []
MG = []


arm_name = 'Armature'
icon_a = 8
icon_k = 0



if bpy.data.objects.find(arm_name) != -1:
armature = bpy.data.objects[arm_name]
BB = armature.data.bones[:]


def bone_index( b ):
for i in range(len(BB)):
if b == BB[i]:
return i
return -1


def get_MG( i ):
M = ML[i] @ MB[i] @ MI[i]
if KK[i] < 0:
return M
else:
return get_MG(KK[i]) @ M


def root_path( _f_name = '' ):
s = os.path.dirname(bpy.data.filepath)
return s + '\\' + _f_name


def name_compat(name):
if name is None:
return 'None'
else:
return name.replace(' ', '_')


def mesh_triangulate(me):
import bmesh
bm = bmesh.new()
bm.from_mesh(me)
bmesh.ops.triangulate( bm , faces = bm.faces )
bm.to_mesh( me )
bm.free()


def aa_add(aa,a):
for i in range(len(aa)):
if a == aa[i]:
return i
aa.append(a)
return len(aa)-1


def get_mat_tex(m):
mat_wrap = node_shader_utils.PrincipledBSDFWrapper(m)
tex_wrap = getattr( mat_wrap , "base_color_texture" , None )
if tex_wrap:
image = tex_wrap.image
if image:
d0 = os.path.dirname(bpy.data.filepath)
d1 = os.path.normpath( d0 + image.filepath )
d2 = os.path.relpath( d1 , start = d0 )
return d2
return 'none'


def get_w2(v):
gg = v.groups[:]
if len(gg) == 0:
return ( 0 , 1.0 ) , ( 0 , 0.0 )
if len(gg) == 1:
return ( gg[0].group , 1.0 ) , ( 0 , 0.0 )
if len(gg) > 2:
gg.sort( key = lambda g: -g.weight )
w1 = gg[0].weight
w2 = gg[1].weight
ww = w1 + w2
return ( gg[0].group , w1/ww ) , ( gg[1].group , w2/ww )


def read_mesh(o):

mesh_triangulate(o)
o.calc_normals_split()

vv_count = len(VV)
LL = o.loops
tt = o.uv_layers.active.data[:]

def loop_to_ii(L):
return LL[L].vertex_index + vv_count , aa_add( TT , tt[L].uv ) , aa_add( NN , LL[L].normal )

gg = [ [[],m,0] for m in o.materials ]
for f in o.polygons:
f_ii = tuple( loop_to_ii(L) for L in f.loop_indices )
gg[f.material_index][0].append( f_ii )

VV.extend( o.vertices[:] )

for g in gg:
if len(g[0]) > 0:
g[2] = aa_add( MM , g[1] )
GG.append(g)


def mesh_to_aa():
for o in oo:
if o.type == "MESH":
read_mesh(o.to_mesh())
for i in range(len(MM)):
tex_name = get_mat_tex(MM[i])
tex_index = aa_add( II , tex_name )
MM[i] = MM[i] , tex_index


def write_arm( fw ):

def write_m( M ):
t = M.to_translation()
e = M.to_euler()
fw( pack('3f', M[0][3] , M[1][3] , M[2][3] ) )
fw( pack('3f', e[0] , e[1] , e[2] ) )

# icon pos
fw( pack( '2B' , icon_a , icon_k ) )

# BB
fw( pack( 'i' , len(BB) ) )
for b in BB:
i = bone_index( b.parent )
KK.append(i)
fw( pack('i', i ) )

# t_pose
for i in range(len(BB)):
b = BB[i]
M = b.matrix_local.copy()
ML.append( M.copy() )
MI.append( M.inverted().copy() )
MB.append( None )
write_m(M)

# t_anim
fw( pack( 'i' , len(bpy.data.actions)-1 ) )
for i in range( 1 , len(bpy.data.actions) ):

a = bpy.data.actions[i]
cc = a.fcurves
a_len = len( cc[0].keyframe_points )

fw( pack('B',len(a.name)) )
fw( bytes( a.name , 'ansi' ) )

fw( pack( 'i' , a_len ) )

for j in range( a_len ):
for k in range(len(BB)):
b = BB[k]
path_p = 'pose.bones["' + b.name + '"].location'
path_q = 'pose.bones["' + b.name + '"].rotation_quaternion'
pp = []
qq = []
for c in cc:
if c.data_path == path_p:
pp.append(c)
if c.data_path == path_q:
qq.append(c)
v = [c.keyframe_points[j].co[1] for c in pp]
p = mathutils.Matrix.Translation( ( v[0] , v[1] , v[2] ) )
v = [c.keyframe_points[j].co[1] for c in qq]
q = mathutils.Quaternion( ( v[0] , v[1] , v[2] , v[3] ) )
MB[k] = p @ q.to_matrix().to_4x4()
for k in range(len(BB)):
M = get_MG(k)
write_m(M)



def save_arm( f_name ):
f = open( f_name , 'bw' )
fw = f.write
write_arm( fw )
f.close()



def save_to_b2m( f_name , s_format ):

mesh_to_aa()

f = open(f_name,'bw')
fw = f.write

# format
fw( pack( 'B' , s_format ) )

# images
fw( pack( 'i' , len(II) ) )
for I in II:
fw( pack('B',len(I)) )
fw( bytes( I , 'ansi' ) )

# mat
fw( pack( 'i' , len(MM) ) )
for M in MM:
fw( pack( 'i' , M[1] ) )
s = M[0].name
fw( pack( 'B' , len(s) ) )
fw( bytes( s , 'ansi' ) )

# VV
fw( pack( 'i' , len(VV) ) )
for v in VV:
fw( pack('3f', v.co[0] , v.co[1] , v.co[2] ) )

# TT
fw( pack( 'i' , len(TT) ) )
for t in TT:
fw( pack('2f', t[0] , t[1] ) )

# NN
fw( pack( 'i' , len(NN) ) )
for n in NN:
fw( pack('3f', n[0] , n[1] , n[2] ) )

# GG
fw( pack( 'i' , len(GG) ) )
for G in GG:
fw( pack( 'i' , G[2] ) )
fw( pack( 'i' , len(G[0]) ) )
for g in G[0]:
#fw( pack( '9i', g[0][0],g[0][1],g[0][2], g[1][0],g[1][1],g[1][2], g[2][0],g[2][1],g[2][2] ) )
fw( pack( '9H', g[0][0],g[0][1],g[0][2], g[1][0],g[1][1],g[1][2], g[2][0],g[2][1],g[2][2] ) )

# animation ...
if s_format < 1:
f.close()
return

# WW
for v in VV:
w = get_w2(v)
fw( pack('<BfBf', w[0][0] , w[0][1] , w[1][0] , w[1][1] ) )

if s_format == 2:
write_arm( fw )

f.close()


save_to_b2m( root_path('m1.b2m') , 2 )
#save_arm( 'm1.arm' )

JernejL
13-12-2021, 06:54 AM
Thanks, i will try to make sense of that, it looks like a simple exporter with clearly separated structures, t-pose is also exported so each model has its own t-pose (does this cause you issues if you want to share animations between multiple modems? )

rts111
14-12-2021, 03:44 PM
Yes, I am using several models with the same armature, and only one animation collection for all models.

In fact, t-pose is not used in any way when imported into the game engine. This function in the script only serves to initialize the matrices. The values did not need to be written to the file.

In the engine, I get matrices directly from positions and rotations:


procedure mat4.AssignEulerTranslation( e,t :vec3 );
var
cy,sy,cp,sp,cr,sr :float;
q :vec4;
begin

LoadIdentity;

cy := cos(e.z * 0.5);
sy := sin(e.z * 0.5);
cp := cos(e.y * 0.5);
sp := sin(e.y * 0.5);
cr := cos(e.x * 0.5);
sr := sin(e.x * 0.5);

q.w := cy * cp * cr + sy * sp * sr;
q.x := cy * cp * sr - sy * sp * cr;
q.y := sy * cp * sr + cy * sp * cr;
q.z := sy * cp * cr - cy * sp * sr;

with q do // m3 AssignQuaternion
begin
e00 := 1.0 - ( y * y + z * z ) * 2.0;
e01 := ( x * y + w * z ) * 2.0;
e02 := ( x * z - w * y ) * 2.0;

e10 := ( x * y - w * z ) * 2.0;
e11 := 1.0 - ( x * x + z * z ) * 2.0;
e12 := ( y * z + w * x ) * 2.0;

e20 := ( x * z + w * y ) * 2.0;
e21 := ( y * z - w * x ) * 2.0;
e22 := 1.0 - ( x * x + y * y ) * 2.0;
end;

e30 := t.x;
e31 := t.y;
e32 := t.z;

end;


For animation, I send these matrices to a simple shader like this:


uniform mat4 mm[32];
attribute vec3 vv;
attribute vec3 nn;
attribute vec2 tt;
attribute ivec2 bb;
attribute vec2 ww;
varying vec2 tex_coord;
varying vec3 normal;
void main( void )
{
ivec2 ibb = ivec2( bb );
vec4 v1 = vec4( vv , 1.0 );
vec4 v2 = ( mm[ibb[0]] * v1 ) * ww[0] + ( mm[ibb[1]] * v1 ) * ww[1];
vec4 n1 = vec4( nn , 0.0 );
vec4 n2 = ( mm[ibb[0]] * n1 ) * ww[0] + ( mm[ibb[1]] * n1 ) * ww[1];
normal = normalize( gl_NormalMatrix * n2.xyz );
tex_coord = tt;
gl_Position = gl_ModelViewProjectionMatrix * v2;
}

Matthias
20-07-2022, 05:33 PM
I tried the game. It is really awsome! Excellent work, from an design- but also from a programming stand point. The Age of Empires controls and sounds are great, too! - I wonder, as it is MMORTS, what happens if I log off and someone attacks. I will be an easy target in this case, right?

The source code is not available, right?

Best regards
Matthias

Jonax
26-07-2022, 12:27 PM
Awesome. Well done!

rts111
27-07-2022, 05:17 AM
Matthias, Jonax - thanks.



I wonder, as it is MMORTS, what happens if I log off and someone attacks. I will be an easy target in this case, right?

Yes, at any moment any other player will be able to attack you. For protection, you can build defensive structures such as towers and walls. The wall has a lot of HP, absorbs and reflects most of the damage, and can be upgraded as long as there are enough resources.



The source code is not available, right?

Sorry, in order to prevent desync, the code will not be available. It's a multiplayer game, after all.

Matthias
30-07-2022, 02:18 PM
Matthias, Jonax - thanks.


Yes, at any moment any other player will be able to attack you. For protection, you can build defensive structures such as towers and walls. The wall has a lot of HP, absorbs and reflects most of the damage, and can be upgraded as long as there are enough resources.

I see, that is what I expected and I like this.



Sorry, in order to prevent desync, the code will not be available. It's a multiplayer game, after all.


Never mind, but what do you mean by "prevent desync"?

rts111
30-07-2022, 06:36 PM
I meant that if some player has access to the code, and if he changes the code of his game client, for example, increases the HP of a unit, then his client will work out of sync with other players' game clients. Something like that.

SilverWarior
30-07-2022, 08:21 PM
I meant that if some player has access to the code, and if he changes the code of his game client, for example, increases the HP of a unit, then his client will work out of sync with other players' game clients. Something like that.

So this would mean you are using distributed processing of game state where each client is processing state of its own ingame units. Right?

rts111
01-08-2022, 04:38 AM
To make the server work as easy as possible, a significant part of the calculations has been transferred to the client side. Yes, something like distributed processing.

SilverWarior
01-08-2022, 03:32 PM
I bet many of PGD members would like to peek into your code to see how you managed to do this. And not for intention of cheating your game but instead to get idea of how to tackle something like this in our current or future projects ;)

rts111
01-08-2022, 09:19 PM
Judging by myself, I would definitely try to hack.

Jonax
02-08-2022, 10:10 AM
Judging by myself, I would definitely try to hack.

Me too :D just for learning about what is possible of course :)

SilverWarior
02-08-2022, 03:17 PM
Judging by myself, I would definitely try to hack.


Me too :D just for learning about what is possible of course :)

I believe I would not go this far and try to hack the game. I don't think this is moral. So instead I would rather see developers to share their knowledge willingly with me.