Multiplayer Enemies

Xer0botXer0

Senpai
Im not understanding this very well,

Your server is also a client is it not ? so it has access to the same game room ?
From what I can see you're creating the zombies on the server then you want the clients to see them too. oh I see.

I'm actually at the same step within my project, spawning instances. And the way I'll be dealing with it is to pass the coordinates and an id, I dont pass the obj_zombie id, because it may be different on the client, I simply pass a value that represents which object it is, so if you pass the value 0, the client will see, okay event type 5, first value received represents which instance to create, the value is 0, and 0 when I compare it to a 2d array, is a zombie..
How does the client know this ?


Create event:
//no need to use a loop, manually update these.
arr_predefined_instances[0,0] = 0 //id
arr_predefined_instances[0,1] = obj_zombie
arr_predefined_instances[1,0] = 1 //id
arr_predefined_instances[1,1] = obj_vampire


case 5: //5 Means A New Enemy Was Created
zombieid = buffer_read(buffer,buffer_u16);
var enemyx = buffer_read(buffer,buffer_u16);
var enemyy = buffer_read(buffer,buffer_u16);

for (i = 0; i < array_height_2d(arr_predefined_instances); i ++)
{
if zombieid == arr_predefined_instances[i,0]
{
instance_create(enemyx ,enemyy, arr_predefined_instances[i,1])​
}​
}

Hope this helps, also how do I use the script tag ?
 
I

IndieCrypt

Guest
Hope this helps, also how do I use the script tag ?
Located left of the 'Drafts' button (Floppy Disk) you will see a newspaper looking symbol... Select this then select 'Code' in the drop down box...
 

Xer0botXer0

Senpai
So server creates zombie with three properties passed, id,x and y.
Clients receive this and create the zombie client side with those coordinates.

Zombie object on server side begins to move, then passes three values, a specific zombies id, it's x pos and y pos to the client, client reads it, finds the zombie instance and updates it.

One thing that sticks out is optimization in regards to zombie movement and how often that's updated.
The type of movement ai has effects how often it needs to be updated.
 

Xer0botXer0

Senpai
Yes means you can read from it, reference it but not write/change it. In your above code you're only reading from it.
 
L

lovareth

Guest
you can try to create your own id system for easy server to client referencing because you cannot simply get the zombie id from server side than passes to client side because built in id is read only.
 

Xer0botXer0

Senpai
Are there absolutely no arrays in obj_spawn or scr_check_spawn ?

where is this ?

enemy[0,0] = enemies; //The enemyid Of The Enemy
enemy[0,1] = enemy.x; //The x Coordinate Of The Enemy With The enemyid Of 1
enemy[0,2] = enemy.y; //The y Coordinate Of The Enemy With The enemyid Of 1
 
L

lovareth

Guest
i can share my simple server - client id system for easy referencing but i currently haven't use my laptop for 2 days..maybe 8 hours from now? if you can wait
 
P

pixelgriffin

Guest
Syncing a world between clients is a tricky subject. It sounds like everyone is suggesting you to constantly update a zombie's position. Naturally, send an ID as the head of any packet involving an enemy so you know which zombie to update, but it's generally better to not constantly send packet updates.

In general I find it is better to send a target and occasionally send sync updates as opposed to constant positions etc. In this case:
- server spawns a zombie and broadcasts it to clients
- server zombie decides a point it wants to walk towards and broadcasts it to clients
- client side zombies hold the same AI and will potentially move similarly
- at certain tick intervals (20 seems to be a popular tick number) send world sync updates to clients
* sync packets can hold exact server-side x,y positions etc.
- client receives sync packet and interpolates between the client side position and the position received from the sync update
 
L

lovareth

Guest
@pixelgriffin i haven't thinking about only sending target x,y to client! that will save a lot of packets. btw i think OP problem right now is how to diferentiate multiple zombies across server and client. maybe you have a simpler code? :)
 
L

lovareth

Guest
initialize global.iid = 0; somewhere in the beginning


///zombie create event
Code:
iid=0;      //create instance iid variable
if (global.if_server){

    iid=global.iid;
    global.iid++; //global.iid will increase after each instance create on server!

    //buffer create
    var buffer = buffer_create(2, buffer_grow,1);
    buffer_seek(buffer, buffer_seek_start, 0);
    buffer_write(buffer, buffer_u8, 20)// msg id, i use 20 for create instance at client side
    buffer_write(buffer, buffer_string, string(object_get_name(object_index)));// use asset_get_index on client side
    buffer_write(buffer, buffer_u32, iid)//id for server - client
    buffer_write(buffer, buffer_f32, x);
    buffer_write(buffer, buffer_f32, y);
    network_send_packet(global.socket, buffer, buffer_tell(buffer));
    alarm[0] = 1; //can use alarm to send packet repeatedly
}

///client networking
......read buffer
Code:
case 20:
    var obj_v1 = buffer_read(buff, buffer_string); //instance name in string
    var obj_v2 = buffer_read(buff, buffer_u32); //this is the custom id
    var obj_v3 = buffer_read(buff, buffer_f32); // x
    var obj_v4 = buffer_read(buff, buffer_f32); // y

    var obj = instance_create(obj_v3, obj_v4, asset_get_index(obj_v1));
    obj.iid = obj_v2; // see that the initial iid is 0 in zombie instance but changed here
    obj.x = obj_v3; //set x position
    obj.y = obj_v4; //set y position
break
later you can send again more packet containing instance name, iid, updated x and y AND easily reference it by code below:

///client networking
Code:
case 21:
    var obj_v1 = buffer_read(buff, buffer_string); //instance name in string
    var obj_v2 = buffer_read(buff, buffer_u32); //this is the custom id
    var obj_v3 = buffer_read(buff, buffer_f32); // x
    var obj_v4 = buffer_read(buff, buffer_f32); // y
    with(asset_get_index(obj_v1)){
        if iid == obj_v2{
            x = obj_v3;
            y = obj_v4;
        }
    }
break
both zombie will have same custom id! you can even add more data such as image properties and also send signal to destroy client zombie! with this, it will applied to all zombies you create in server side!
hope it can give you some direction!
 
Last edited by a moderator:

FrostyCat

Redemption Seeker
This came up, even though in my entire game, the only arrays i've ever used in my game so far is the alarm and async_load, which is not even in my obj_spawn or scr_check_spawn... soooooooo confused right now! :(
Newsflash: async_load is never an array.

If you treated it as one anywhere in the game, stop that. Read the Manual entry on the asynchronous event you used and handle it properly.
 

Roa

Member
You shouldn't need to do much of anything honestly. Send a packet on create and send a packet to the other player when its hit or destroyed. If the AI is the same, there is zero reason is should d-sync and even if it does, who is going to miss one stray zombie going a few pixels over?

A very simple solution is to send the servers instance id for that zombie and use a ds_map to map that to a correlating zombie on the client.

Something I like to do that I highly don't recommend for everyone, is make a child object of a parent enemy to where each possible enemy is a unique object type. You will have a lot of instance, but its really great actually because editing the parents works for all sub objects, you can readily ID them across all clients and you have the added bonus of re-using those enemy objects and give them new properties to be new monsters without dealing with making new IDs and creating/destroying instances. The draw backs to this system is that it is a little more labor on you and only works for a few enemies, I cap at 16, but that's a lot honestly. I've done this for players too. In certain situations, its just easier than using a bunch of list, maps and for loops of instances.

I don't tell a lot of people that because I've been told it's asinine but it works for me. Less work on the CPU too to not have to cycle through a bunch of variables stored in instances.
 
Last edited:
L

lovareth

Guest
@Roa your advice sound great for an online multiplayer which is sending packet should be minimize as we can. i will try to apply that way next time, but for now i will stick with myself XD. btw we havent heard any update from OP yet
 
P

pixelgriffin

Guest
You shouldn't need to do much of anything honestly. Send a packet on create and send a packet to the other player when its hit or destroyed. If the AI is the same, there is zero reason is should d-sync and even if it does, who is going to miss one stray zombie going a few pixels over?
Take care about that statement! Desync can cause mass divergence very easily. There is plenty of reason even if the AI is the same that divergence can occur: difference in hardware, packet latency and loss etc. Small divergence becomes big divergence over time. This is why it's important to periodically sync everything together when necessary.

@Ben Hubble I know it's a lot of text but this helps me a lot when considering network design for games.
 

Roa

Member
Take care about that statement! Desync can cause mass divergence very easily. There is plenty of reason even if the AI is the same that divergence can occur: difference in hardware, packet latency and loss etc. Small divergence becomes big divergence over time. This is why it's important to periodically sync everything together when necessary.
Yeah, but this only lies true if you use hardware related calls such as random() or choose(). If the AI is all algorithmic then it should always play out the same. and even in cases of latency, its very rare for massive desyncs, but a client could resolve for this very easy by dividing latency by frame count and deciding how many frames to repeat an action till its caught up on this algorithmic AI, the most basic client prediction. Its not that big of a deal until you start expecting erratic outputs such as choosing random states, targets, or player input. That said, you're right to a degree. There should always be some kind of syncing, but you shouldn't need it nearly as much here. But the more you start syncing, the more client prediction you have to ad,because syncing pulls the clients millsecs back, you have to find creative and effective ways to hide the latency.
 
Last edited:
P

pixelgriffin

Guest
Yeah, but this only lies true if you use hardware related calls such as random() or choose(). If the AI is all algorithmic then it should always play out the same
It's also true if you are using a non-deterministic engine like Game Maker. Floating point non-determinism can result in huge divergence. It is particularly important in this case since it sounds like there would be many zombies.

Client side interpolation between a received state and the client state is just a part of networking, always. Increasing sync rates doesn't mean more complicated interpolation.

@Ben Hubble I think your issue is that you are using instance_find to get your enemy. You should probably be storing the IDs in the enemy objects, and then iterating over enemy objects until you find the object with the right ID. The ID you are assigning should not be Game Maker's "id" variable. It should be something you make yourself. Because it's not Game Maker's "id" you cannot use things like instance_find.
 

Xer0botXer0

Senpai
What's going on here ?
Code:
///scr_update_enemy_pos(enemyid,x,y)

enemyid = argument0;
enemyx = argument1;
enemyy = argument2;

var i;
  for(i = 0; i <= global.enemies_count; i++)
  {
  enemy = instance_find(obj_enemy,i);
  if(enemy != noone) {
  enemyid_check = enemy.enemyid;
  if(enemyid == enemyid_check) {
  with(enemy) {
  x = obj_network.enemyx;
  y = obj_network.enemyy;
  }
  }
  }
  }
You've got global.enemies_count, is that being incremented when a new enemy instance is added ? and dec when removed ?
Okay so for loop, find the instance to update, if the instance exists(I suppose that's what if enemy != noone does) then set enemyid_check to enemy.enemyid..
if enemyid == enemyid_check.. then update

This may be where the problem is..

create a breakpoint by this if statement, and then within the if statement, run the game in debug mode, let your game reach that if satement then compare the two variables "enemyid" and "enemyid_check".

also why are you updating obj_network, is that the enemy object ?
 
Top