sbischoff-ai / pygase

A Python package that contains a high-performance, versatile UDP-based game server, client and network protocol with a simple API.
MIT License
43 stars 7 forks source link

Getting started / Gamestate doesn't expose position and hp #6

Closed shazz closed 3 years ago

shazz commented 4 years ago

Hi, I just tried the getting started tutorial but I have a bug, it seems the serialized GameState object returned by client.access_game_state() doesn't expose the additional attributes (position and hp) added into the initial game state.

While calling

with client.access_game_state() as game_state:
    print(game_state.position)
    print(game_state.hp)

I got:

Traceback (most recent call last):
  File "client.py", line 10, in <module>
    print(game_state.position)
AttributeError: 'GameState' object has no attribute 'position'

I typed dir(game_state), no position or hp attributes...

Thanks!

Python version: 3.7 Pygase version: 0.3.1

shazz commented 4 years ago

By the way, I noticed that using:

position = client.try_to(lambda game_state: game_state.position)
with client.access_game_state() as game_state:
    print(dir(game_state))

it works but I don't understand why the access_game_state() doesn't work before the try_to as the position is updated before the client connects so it should already exist no ?

Client code:

from pygase import Client
import time

client = Client()
client.connect_in_thread(port=8080)

client.register_event_handler("ATTACK_FEEDBACK", print)

with client.access_game_state() as game_state:
    print(dir(game_state))
    # print(game_state.position)
    # print(game_state.hp)

while True:
    print(client.try_to(lambda game_state: game_state.position))
    with client.access_game_state() as game_state:
        print(dir(game_state))
    # client.dispatch_event("ATTACK", attack_position=i*2.0/5-1)
    # time.sleep(2)

client.disconnect(shutdown_server=True)    

Server code:

# backend.py

import math
from pygase import GameState, Backend

### SETUP ###

# Let there be an imaginary enemy at position 0 with 100 health points.
initial_game_state = GameState(
    position=0, 
    hp=100
)

# Define what happens in an iteration of the game loop.
def time_step(game_state, dt):
    # Make the imaginary enemy move in sinuous lines like a drunkard.
    new_position = game_state.position + math.sin(dt)
    print(game_state.position)
    return {"position": new_position}

# Create the backend.
backend = Backend(initial_game_state, time_step)

### EVENT HANDLERS ###

def on_attack(attack_position, client_address, game_state, **kwargs):
    # Check if the attack landed near enough to hit.
    if abs(attack_position - game_state.position) < 0.1:
        backend.server.dispatch_event("ATTACK_FEEDBACK", "Hit!", target_client=client_address)
        # Subtract the attacks damage from the enemies health.
        return {"hp": game_state.hp - 10}
    backend.server.dispatch_event("ATTACK_FEEDBACK", "Missed.", target_client=client_address)
    return {}

backend.game_state_machine.register_event_handler("ATTACK", on_attack)

### MAIN PROCESS ###

if __name__ == "__main__":
    backend.run('localhost', 8080)
sbischoff-ai commented 3 years ago

Sorry for answering almost a year late, I was busy and didn't check my GitHub. I'm sure this is hardly even relevant to you anymore, but maybe this could help someone else.

The behavior you describe is what is expected with your client code:


client.register_event_handler("ATTACK_FEEDBACK", print)

with client.access_game_state() as game_state:
    print(dir(game_state))
    # print(game_state.position)
    # print(game_state.hp)

The line print(dir(game_state)) is executed before the first game state update ever reaches the client. Even though you probably run the server on the same computer and the connection is via localhost and not an actual network, the client process will still be much much faster executing a few lines of python code than the game loop will be exchanging packages between server and client. That is exactly what the try_to method is for, since your client will need to get started somehow. In the chase example game you can see I have handled that by waiting for the player object to be created server_side (the while client.player_id is None loop before the clients actual game loop).

You can set the log level to DEBUG and add a line of logging before and after your try to access the game_state object to see what the client connection thread did up to the point where you try to access the position or hp attributes. Simply adding sleep(1.0) before with client.access_game_state() as game_state: would also make you code work btw.