Skip to content

Commit

Permalink
- updated AlienInvaders game
Browse files Browse the repository at this point in the history
- cleaned up Docker files
  • Loading branch information
sven1977 committed Jan 21, 2018
1 parent 804f789 commit a406c81
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 30 deletions.
6 changes: 5 additions & 1 deletion Plugins/MaRLEnE/Scripts/marlene_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,11 @@ async def new_client_connected(reader, writer):
break
unpacker.feed(data)
for message in unpacker:
response = manage_message(message, writer)
if not isinstance(message, dict):
response = {"status": "error", "message": "Unknown message type ({})!".format(type(message).__name__)}
else:
response = manage_message(message, writer)

# write back immediately
if response:
send_message(response, writer)
Expand Down
32 changes: 19 additions & 13 deletions Plugins/MaRLEnE/Scripts/server_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,15 @@ def sanity_check_observer(observer, playing_world):
return observer.GetAttachParent(), obs_name


def get_scene_capture_and_texture(parent, obs_name, width=84, height=84):
def get_scene_capture_and_texture(parent, observer):
"""
Adds a SceneCapture2DComponent to some parent camera object so we can capture the pixels for this camera view.
Then captures the image, renders it on the render target of the scene capture and returns the image as a numpy
array.
:param uobject parent: The parent camera/scene-capture actor/component to which the new SceneCapture2DComponent
needs to be attached or for which the render target has to be created and/or returned.
:param str obs_name: The name of the observer component.
:param int width: The width (in px) to use for the render target.
:param int height: The height (in px) to use for the render target.
:param uobject observer: The MLObserver uobject.
:return: numpy array containing the pixel values (0-255) of the captured image.
:rtype: np.ndarray
"""
Expand All @@ -115,22 +113,25 @@ def get_scene_capture_and_texture(parent, obs_name, width=84, height=84):
scene_capture.bCaptureOnMovement = False
# error -> return nothing
else:
raise RuntimeError("Observer {} has bScreenCapture set to true, but is not a child of either a Camera or a SceneCapture2D!".format(obs_name))
raise RuntimeError("Observer {} has bScreenCapture set to true, but is not a child of either a Camera or "
"a SceneCapture2D!".format(observer.get_name()))

if not texture:
# TODO: setup camera transform and options (greyscale, etc..)
texture = scene_capture.TextureTarget = ue.create_transient_texture_render_target2d(width, height)
# use MLObserver's width/height settings
texture = scene_capture.TextureTarget =\
ue.create_transient_texture_render_target2d(observer.Width or 84, observer.Height or 84)
ue.log("DEBUG: scene capture is created in get_scene_image texture={}".format(scene_capture.TextureTarget))

return scene_capture, texture


def get_scene_capture_image(scene_capture, texture):
def get_scene_capture_image(scene_capture, texture, gray_scale=False):
"""
Takes a snapshot through a SceneCapture2DComponent and its Texture target and returns the image as a numpy array.
:param uobject scene_capture: The SceneCapture2DComponent uobject.
:param uobject texture: The TextureTarget uobject.
:param bool gray_scale: Whether to transform the image into gray-scale before returning.
:return: numpy array containing the pixel values (0-255) of the captured image
:rtype: np.ndarray
"""
Expand All @@ -141,6 +142,9 @@ def get_scene_capture_image(scene_capture, texture):
byte_string = bytes(texture.render_target_get_data()) # use render_target_get_data_to_buffer(data,[mipmap]?) instead
np_array = np.frombuffer(byte_string, dtype=np.uint8) # convert to pixel values (0-255 uint8)
img = np_array.reshape((texture.SizeX, texture.SizeY, 4))[:, :, :3] # slice away alpha value
# do a simple dot product to get the gray-scaled image
if gray_scale:
img = np.dot(img, [0.299, 0.587, 0.114])

return img

Expand Down Expand Up @@ -194,10 +198,10 @@ def compile_obs_dict(reward=None):
# this observer returns a camera image
if observer.bScreenCapture:
try:
scene_capture, texture = get_scene_capture_and_texture(parent, obs_name)
scene_capture, texture = get_scene_capture_and_texture(parent, observer)
except RuntimeError as e:
return {"status": "error", "message": "{}".format(e)}
img = get_scene_capture_image(scene_capture, texture)
img = get_scene_capture_image(scene_capture, texture, observer.bGrayscale)
_OBS_DICT[obs_name + "/camera"] = img

for observed_prop in observer.ObservedProperties:
Expand Down Expand Up @@ -273,11 +277,13 @@ def get_spec():
# this observer returns a camera image
if observer.bScreenCapture:
try:
_, texture = get_scene_capture_and_texture(parent, obs_name)
_, texture = get_scene_capture_and_texture(parent, observer)
except RuntimeError as e:
return {"status": "error", "message": "{}".format(e)}
observation_space_desc[obs_name+"/camera"] = {"type": "IntBox", "shape": (texture.SizeX, texture.SizeY, 3),
"min": 0, "max": 255}
observation_space_desc[obs_name+"/camera"] = {
"type": "IntBox",
"shape": (texture.SizeX, texture.SizeY) if observer.bGrayscale else (texture.SizeX, texture.SizeY, 3),
"min": 0, "max": 255}

# go through non-camera/capture properties that need to be observed by this Observer
for observed_prop in observer.ObservedProperties:
Expand Down
9 changes: 9 additions & 0 deletions docker/Dockerfile_tensorforce
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# this is a container for running pycharm apps against tensorforce from
FROM ubuntu:xenial
RUN apt-get update -y
RUN apt-get install -y --no-install-recommends tzdata python3 python3-dev python3-pip python3-setuptools git dos2unix net-tools iproute2 iputils-ping telnet
RUN apt-get install -y python3-setuptools --upgrade
RUN apt-get install -y build-essential
RUN pip3 install wheel numpy msgpack msgpack-numpy pydevd tensorflow tensorforce dm-sonnet jupyter notebook cached_property scipy cython matplotlib pygame msgpack-python pillow virtualenv virtualenvwrapper python-dateutil gym gym[atari]

CMD ["bash"]
24 changes: 24 additions & 0 deletions docker/ue4_base/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# to build the initial UE4 Engine without any game
# available on docker hub as ducandu/ue4

FROM ubuntu:xenial
RUN apt-get update -y && apt-get install -y --no-install-recommends tzdata shared-mime-info libnss3 libxss1 libasound2 python3 python3-dev python3-pip sudo vim git mono-reference-assemblies-4.0 mono-devel mono-xbuild mono-mcs mono-dmcs libmono-system-data-datasetextensions4.0-cil libmono-system-web-extensions4.0-cil libmono-system-management4.0-cil libmono-system-xml-linq4.0-cil libmono-microsoft-build-tasks-v4.0-4.0-cil cmake dos2unix clang-5.0 clang-3.8 libqt4-dev git build-essential ca-certificates pkg-config bash-completion
RUN apt-get install -y python3-setuptools --upgrade
RUN pip3 install numpy msgpack msgpack-numpy pydevd
# add some ue4 user (UE4 does not allow building with root)
RUN adduser --disabled-password --home /home/ue4 --shell /bin/bash ue4 && usermod -a -G sudo ue4

# change to new user
USER ue4
WORKDIR /home/ue4

# git all needed repos
RUN git clone https://18920f59b27e7c4b79fbef856af2152612e1f471@github.com/EpicGames/UnrealEngine.git
WORKDIR UnrealEngine
RUN ./Setup.sh
RUN ./GenerateProjectFiles.sh -engine

RUN make

CMD ["bash"]

36 changes: 36 additions & 0 deletions docker/ue4_exec_game/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
ARG base_container=base_container
FROM ducandu/ue4_game AS game
RUN echo " "


# new container based on Ubuntu
FROM ubuntu:xenial

# install minimal python tools to be able to run plugin UnrealEnginePython
RUN apt-get update -y && apt-get install -y --no-install-recommends python3 python3-dev python3-pip python3-setuptools
RUN pip3 install numpy msgpack msgpack-numpy pydevd

# add the game user
RUN adduser --disabled-password --home /home/ue4 --shell /bin/bash ue4 && usermod -a -G sudo ue4
# change to new user
USER ue4
WORKDIR /home/ue4
RUN mkdir -p Games/AlienInvaders
WORKDIR Games/AlienInvaders

# just copy the cooked game
COPY --from=game --chown=ue4:ue4 /home/ue4/UnrealEngine/AlienInvaders/Build/LinuxNoEditor ./
# and the python scripts
RUN mkdir -p AlienInvaders/Content/Scripts
COPY --from=game --chown=ue4:ue4 /home/ue4/UnrealEngine/AlienInvaders/Content/Scripts AlienInvaders/Content/Scripts/

#USER root
#RUN chmod -R 0777 /home/ue4/Games/AlienInvaders
#USER ue4

# TODO: add PYTHONPATH pointing to Games/.../Content/Scripts to env

# start the game automatically when running this container
# CMD ["/home/ue4/Games/AlienInvaders/AlienInvaders.sh -NullRHI -nosound"]
CMD ["bash"]

30 changes: 30 additions & 0 deletions docker/ue4_incr_game/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# to update and incrementally build/cook a game
# creates a new image based on the game's init image to allow for a fast incremental build/cook
ARG base_container=ue4_alieninvaders

FROM ducandu/$base_container

USER ue4
WORKDIR /home/ue4

# update our repos
WORKDIR marlene
RUN git pull
WORKDIR UnrealEnginePython
RUN git pull

# copy (update; only newer files) the example game and the two plugins into UnrealEngine
WORKDIR /home/ue4
RUN cp -r -u marlene/examples/UE4Games/${GAME} UnrealEngine/.
RUN cp -r -u marlene/Plugins/MaRLEnE UnrealEngine/${GAME}/Plugins/.
RUN cp -r -u UnrealEnginePython UnrealEngine/${GAME}/Plugins/.

WORKDIR UnrealEngine

# update necessary scripts from the plugin to the game's content folder
RUN cp -u ${GAME}/Plugins/MaRLEnE/Scripts/* ${GAME}/Content/Scripts/.

# incrementally build and cook the game
RUN Engine/Build/BatchFiles/RunUAT.sh BuildCookRun -project=${GAME}/${GAME}.uproject -nop4 -build -cook -compressed -stage -platform=Linux -clientconfig=Development -pak -archive -archivedirectory="${GAME}/Build" -utf8output

CMD ["bash"]
36 changes: 36 additions & 0 deletions docker/ue4_init_game/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# to build a initial game on top of the basic (engine) ducandu/ue4 container

# - use docker build --build-arg game=AlienInvaders to build this image
# `docker build --build-arg game=AlienInvaders -l ducandu/ue4_[some_game_name] .`

FROM ducandu/ue4

USER ue4
WORKDIR /home/ue4

# default game; override this via `--build-arg game=` on `docker build` command line
ARG game=AlienInvaders
ENV GAME=$game

# git all needed repos
RUN git clone https://github.com/ducandu/marlene.git
RUN git clone https://github.com/20tab/UnrealEnginePython.git
RUN mkdir -p UnrealEngine/${GAME}/Plugins
# copy the example game and the two plugins into UnrealEngine
RUN cp -r marlene/examples/UE4Games/${GAME} UnrealEngine/.
RUN cp -r marlene/Plugins/MaRLEnE UnrealEngine/${GAME}/Plugins/.
RUN cp -r UnrealEnginePython UnrealEngine/${GAME}/Plugins/.

WORKDIR UnrealEngine

## Modify the UnrealEnginePython build cs file to add the python lib and include paths
# not necessary as ubuntu is covered by the default values in UEPython build cs
#RUN sed -i 's/\/usr\/local\/include\/python3.6//' AlienInvaders/Plugins/UnrealEnginePython/Source/UnrealEnginePython/UnrealEnginePython.Build.cs

# - copy necessary scripts from the plugin to the game's content folder
RUN mkdir -p ${GAME}/Content/Scripts/ && cp ${GAME}/Plugins/MaRLEnE/Scripts/* ${GAME}/Content/Scripts/.

# initial build and cook of the game
RUN Engine/Build/BatchFiles/RunUAT.sh BuildCookRun -project=${GAME}/${GAME}.uproject -nop4 -build -cook -compressed -stage -platform=Linux -clientconfig=Development -pak -archive -archivedirectory="${GAME}/Build" -utf8output

CMD ["bash"]
2 changes: 1 addition & 1 deletion examples/UE4Games/AlienInvaders/AlienInvaders.uproject
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,6 @@
}
],
"TargetPlatforms": [
"Android"
"LinuxNoEditor"
]
}
2 changes: 1 addition & 1 deletion examples/UE4Games/AlienInvaders/Config/DefaultGame.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ Homepage="http://ue4resources.com/"
SupportContact="https://twitter.com/HoussineMehnik"

[/Script/MaRLEnE.MaRLEnESettings]
Address=localhost
Address=0.0.0.0
Port=6025

Binary file not shown.
Binary file modified examples/UE4Games/AlienInvaders/Content/Maps/Map_MainGame.umap
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,11 @@ async def new_client_connected(reader, writer):
break
unpacker.feed(data)
for message in unpacker:
response = manage_message(message, writer)
if not isinstance(message, dict):
response = {"status": "error", "message": "Unknown message type ({})!".format(type(message).__name__)}
else:
response = manage_message(message, writer)

# write back immediately
if response:
send_message(response, writer)
Expand Down
32 changes: 19 additions & 13 deletions examples/UE4Games/AlienInvaders/Content/Scripts/server_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,15 @@ def sanity_check_observer(observer, playing_world):
return observer.GetAttachParent(), obs_name


def get_scene_capture_and_texture(parent, obs_name, width=84, height=84):
def get_scene_capture_and_texture(parent, observer):
"""
Adds a SceneCapture2DComponent to some parent camera object so we can capture the pixels for this camera view.
Then captures the image, renders it on the render target of the scene capture and returns the image as a numpy
array.
:param uobject parent: The parent camera/scene-capture actor/component to which the new SceneCapture2DComponent
needs to be attached or for which the render target has to be created and/or returned.
:param str obs_name: The name of the observer component.
:param int width: The width (in px) to use for the render target.
:param int height: The height (in px) to use for the render target.
:param uobject observer: The MLObserver uobject.
:return: numpy array containing the pixel values (0-255) of the captured image.
:rtype: np.ndarray
"""
Expand All @@ -115,22 +113,25 @@ def get_scene_capture_and_texture(parent, obs_name, width=84, height=84):
scene_capture.bCaptureOnMovement = False
# error -> return nothing
else:
raise RuntimeError("Observer {} has bScreenCapture set to true, but is not a child of either a Camera or a SceneCapture2D!".format(obs_name))
raise RuntimeError("Observer {} has bScreenCapture set to true, but is not a child of either a Camera or "
"a SceneCapture2D!".format(observer.get_name()))

if not texture:
# TODO: setup camera transform and options (greyscale, etc..)
texture = scene_capture.TextureTarget = ue.create_transient_texture_render_target2d(width, height)
# use MLObserver's width/height settings
texture = scene_capture.TextureTarget =\
ue.create_transient_texture_render_target2d(observer.Width or 84, observer.Height or 84)
ue.log("DEBUG: scene capture is created in get_scene_image texture={}".format(scene_capture.TextureTarget))

return scene_capture, texture


def get_scene_capture_image(scene_capture, texture):
def get_scene_capture_image(scene_capture, texture, gray_scale=False):
"""
Takes a snapshot through a SceneCapture2DComponent and its Texture target and returns the image as a numpy array.
:param uobject scene_capture: The SceneCapture2DComponent uobject.
:param uobject texture: The TextureTarget uobject.
:param bool gray_scale: Whether to transform the image into gray-scale before returning.
:return: numpy array containing the pixel values (0-255) of the captured image
:rtype: np.ndarray
"""
Expand All @@ -141,6 +142,9 @@ def get_scene_capture_image(scene_capture, texture):
byte_string = bytes(texture.render_target_get_data()) # use render_target_get_data_to_buffer(data,[mipmap]?) instead
np_array = np.frombuffer(byte_string, dtype=np.uint8) # convert to pixel values (0-255 uint8)
img = np_array.reshape((texture.SizeX, texture.SizeY, 4))[:, :, :3] # slice away alpha value
# do a simple dot product to get the gray-scaled image
if gray_scale:
img = np.dot(img, [0.299, 0.587, 0.114])

return img

Expand Down Expand Up @@ -194,10 +198,10 @@ def compile_obs_dict(reward=None):
# this observer returns a camera image
if observer.bScreenCapture:
try:
scene_capture, texture = get_scene_capture_and_texture(parent, obs_name)
scene_capture, texture = get_scene_capture_and_texture(parent, observer)
except RuntimeError as e:
return {"status": "error", "message": "{}".format(e)}
img = get_scene_capture_image(scene_capture, texture)
img = get_scene_capture_image(scene_capture, texture, observer.bGrayscale)
_OBS_DICT[obs_name + "/camera"] = img

for observed_prop in observer.ObservedProperties:
Expand Down Expand Up @@ -273,11 +277,13 @@ def get_spec():
# this observer returns a camera image
if observer.bScreenCapture:
try:
_, texture = get_scene_capture_and_texture(parent, obs_name)
_, texture = get_scene_capture_and_texture(parent, observer)
except RuntimeError as e:
return {"status": "error", "message": "{}".format(e)}
observation_space_desc[obs_name+"/camera"] = {"type": "IntBox", "shape": (texture.SizeX, texture.SizeY, 3),
"min": 0, "max": 255}
observation_space_desc[obs_name+"/camera"] = {
"type": "IntBox",
"shape": (texture.SizeX, texture.SizeY) if observer.bGrayscale else (texture.SizeX, texture.SizeY, 3),
"min": 0, "max": 255}

# go through non-camera/capture properties that need to be observed by this Observer
for observed_prop in observer.ObservedProperties:
Expand Down

0 comments on commit a406c81

Please sign in to comment.