Major update, made single main file for both multi and single agent, added argsparse, polished everything
This commit is contained in:
parent
6a84d0b3f4
commit
84000dd28b
46 changed files with 1590 additions and 744 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -161,3 +161,6 @@ cython_debug/
|
||||||
|
|
||||||
# Mac bs
|
# Mac bs
|
||||||
.DS_store
|
.DS_store
|
||||||
|
|
||||||
|
# Random stuff
|
||||||
|
__pycache__/
|
||||||
|
|
386
LICENSE
386
LICENSE
|
@ -1,21 +1,373 @@
|
||||||
MIT License
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
Copyright (c) 2023 Vasilis Valatsos
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
1.1. "Contributor"
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
means each individual or legal entity that creates, contributes to
|
||||||
in the Software without restriction, including without limitation the rights
|
the creation of, or owns Covered Software.
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
1.2. "Contributor Version"
|
||||||
copies or substantial portions of the Software.
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
1.3. "Contribution"
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
means Covered Software of a particular Contributor.
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
1.4. "Covered Software"
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
means Source Code Form to which the initial Contributor has attached
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
SOFTWARE.
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||
|
|
48
README.md
48
README.md
|
@ -1,2 +1,46 @@
|
||||||
# PneuMARL
|
|
||||||
A PyGame2 Zelda-like embedded with MARL algorithm.
|
# Pneuma: Reinforcement Learning Platform
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Pneuma is a Reinforcement Learning platform created as part of a thesis project. It is developed using PyGame and offers a customizable environment for testing and implementing reinforcement learning algorithms.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To install Pneuma, clone this repository and install the requirements (`requirements.txt`)
|
||||||
|
|
||||||
|
After cloning, you can edit the agents, create your own, and modify pneuma.py (the main file). Additionally, consider editing player.setup_agent() for further customization.
|
||||||
|
Note
|
||||||
|
|
||||||
|
- [] TODO: Separate the update logic from the network logic inside the player.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To run Pneuma, use the command-line interface with the following options:
|
||||||
|
|
||||||
|
- `--no_seed`: If set to True, runs the program without a seed. Default is False.
|
||||||
|
- `--seed [int]`: Specifies the seed for the random number generator. Default is 1.
|
||||||
|
- `--n_episodes [int]`: Defines the number of episodes. Default is 300.
|
||||||
|
- `--ep_length [int]`: Sets the length of each episode. Default is 5000.
|
||||||
|
- `--n_players [int]`: Number of players. Default is 1.
|
||||||
|
- `--chkpt_path [str]`: Path for saving/loading agent models. Default is "agents/saved_models".
|
||||||
|
- `--figure_path [str]`: Path for saving figures. Default is "figures".
|
||||||
|
- `--horizon [int]`: Number of steps per update. Default is 200.
|
||||||
|
- `--show_pg`: If True, opens a PyGame window on the desktop. Default is False.
|
||||||
|
- `--no_load`: If True, ignores saved models. Default is False.
|
||||||
|
- `--gamma [float]`: The gamma parameter for PPO. Default is 0.99.
|
||||||
|
- `--alpha [float]`: The alpha parameter for PPO. Default is 0.0003.
|
||||||
|
- `--policy_clip [float]`: The policy clip. Default is 0.2.
|
||||||
|
- `--batch_size [int]`: Size of each batch. Default is 64.
|
||||||
|
- `--n_epochs [int]`: Number of epochs. Default is 10.
|
||||||
|
- `--gae_lambda [float]`: The lambda parameter of the GAE. Default is 0.95.
|
||||||
|
|
||||||
|
### Example Command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ python pneuma.py --seed 42 --n_episodes 300 --ep_length 5000 --n_players 2 --no_load
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Pneuma is licensed under the Mozilla Public License 2.0.
|
|
@ -8,15 +8,19 @@ class Agent:
|
||||||
|
|
||||||
def __init__(self, input_dims, n_actions, gamma=0.99, alpha=0.0003,
|
def __init__(self, input_dims, n_actions, gamma=0.99, alpha=0.0003,
|
||||||
policy_clip=0.2, batch_size=64, N=2048, n_epochs=10,
|
policy_clip=0.2, batch_size=64, N=2048, n_epochs=10,
|
||||||
gae_lambda=0.95):
|
gae_lambda=0.95, chkpt_dir='tmp/ppo'):
|
||||||
|
|
||||||
self.gamma = gamma
|
self.gamma = gamma
|
||||||
self.policy_clip = policy_clip
|
self.policy_clip = policy_clip
|
||||||
self.n_epochs = n_epochs
|
self.n_epochs = n_epochs
|
||||||
self.gae_lambda = gae_lambda
|
self.gae_lambda = gae_lambda
|
||||||
|
|
||||||
self.actor = ActorNetwork(input_dims, n_actions, alpha)
|
self.actor = ActorNetwork(
|
||||||
self.critic = CriticNetwork(input_dims, alpha)
|
input_dims, n_actions, alpha, chkpt_dir=chkpt_dir)
|
||||||
|
|
||||||
|
self.critic = CriticNetwork(
|
||||||
|
input_dims, alpha, chkpt_dir=chkpt_dir)
|
||||||
|
|
||||||
self.memory = PPOMemory(batch_size)
|
self.memory = PPOMemory(batch_size)
|
||||||
|
|
||||||
def remember(self, state, action, probs, vals, reward, done):
|
def remember(self, state, action, probs, vals, reward, done):
|
||||||
|
@ -79,17 +83,17 @@ class Agent:
|
||||||
weighted_probs = advantage[batch] * prob_ratio
|
weighted_probs = advantage[batch] * prob_ratio
|
||||||
weighted_clipped_probs = T.clamp(
|
weighted_clipped_probs = T.clamp(
|
||||||
prob_ratio, 1-self.policy_clip, 1+self.policy_clip)*advantage[batch]
|
prob_ratio, 1-self.policy_clip, 1+self.policy_clip)*advantage[batch]
|
||||||
actor_loss = -T.min(weighted_probs,
|
self.actor_loss = -T.min(weighted_probs,
|
||||||
weighted_clipped_probs).mean()
|
weighted_clipped_probs).mean()
|
||||||
|
|
||||||
returns = advantage[batch] + values[batch]
|
returns = advantage[batch] + values[batch]
|
||||||
critic_loss = (returns - critic_value)**2
|
self.critic_loss = (returns - critic_value)**2
|
||||||
critic_loss = critic_loss.mean()
|
self.critic_loss = self.critic_loss.mean()
|
||||||
|
|
||||||
total_loss = actor_loss + 0.5*critic_loss
|
self.total_loss = self.actor_loss + 0.5*self.critic_loss
|
||||||
self.actor.optimizer.zero_grad()
|
self.actor.optimizer.zero_grad()
|
||||||
self.critic.optimizer.zero_grad()
|
self.critic.optimizer.zero_grad()
|
||||||
total_loss.backward()
|
self.total_loss.backward()
|
||||||
self.actor.optimizer.step()
|
self.actor.optimizer.step()
|
||||||
self.critic.optimizer.step()
|
self.critic.optimizer.step()
|
||||||
|
|
||||||
|
|
|
@ -78,11 +78,13 @@ class ActorNetwork(nn.Module):
|
||||||
|
|
||||||
return dist
|
return dist
|
||||||
|
|
||||||
def save_checkpoint(self, filename = 'actor_torch_ppo'):
|
def save_checkpoint(self, filename='actor_torch_ppo'):
|
||||||
T.save(self.state_dict(), os.path.join(self.chkpt_dir, filename))
|
T.save(self.state_dict(), os.path.join(self.chkpt_dir, filename))
|
||||||
|
|
||||||
def load_checkpoint(self, filename = 'actor_torch_ppo'):
|
def load_checkpoint(self, filename='actor_torch_ppo'):
|
||||||
self.load_state_dict(T.load(os.path.join(self.chkpt_dir, filename), map_location=self.device))
|
self.load_state_dict(
|
||||||
|
T.load(os.path.join(self.chkpt_dir, filename),
|
||||||
|
map_location=self.device))
|
||||||
|
|
||||||
|
|
||||||
class CriticNetwork(nn.Module):
|
class CriticNetwork(nn.Module):
|
||||||
|
@ -110,8 +112,10 @@ class CriticNetwork(nn.Module):
|
||||||
value = self.critic(state)
|
value = self.critic(state)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def save_checkpoint(self, filename = 'critic_torch_ppo'):
|
def save_checkpoint(self, filename='critic_torch_ppo'):
|
||||||
T.save(self.state_dict(), os.path.join(self.chkpt_dir, filename))
|
T.save(self.state_dict(), os.path.join(self.chkpt_dir, filename))
|
||||||
|
|
||||||
def load_checkpoint(self, filename = 'critic_torch_ppo'):
|
def load_checkpoint(self, filename='critic_torch_ppo'):
|
||||||
self.load_state_dict(T.load(os.path.join(self.chkpt_dir, filename), map_location=self.device))
|
self.load_state_dict(
|
||||||
|
T.load(os.path.join(self.chkpt_dir, filename),
|
||||||
|
map_location=self.device))
|
||||||
|
|
BIN
agents/saved_models/A0
Normal file
BIN
agents/saved_models/A0
Normal file
Binary file not shown.
BIN
agents/saved_models/C0
Normal file
BIN
agents/saved_models/C0
Normal file
Binary file not shown.
1
agents/saved_models/README.md
Normal file
1
agents/saved_models/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# This is a folder with all the saved models.
|
|
@ -5,40 +5,40 @@
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,392,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,392,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,391,-1,-1,-1,391,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,391,-1,-1,-1,391,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,391,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,391,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,400,400,-1,-1,400,-1,400,-1,-1,400,-1,-1,-1,400,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,500,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,500,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,390,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,393,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,393,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,400,-1,-1,400,-1,-1,-1,-1,400,-1,-1,-1,390,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,391,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,390,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,391,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,400,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,393,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,392,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,392,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
|
|
|
|
@ -3,11 +3,11 @@
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,395,395,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,395,395,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,395,-1,-1,395,395,395,395,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,395,-1,-1,395,395,395,395,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,395,395,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,395,395,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,395,395,395,395,395,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,395,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,700,-1,395,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,395,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,395,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,-1,-1,395,395,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,395,395,395,395,395,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,395,395,395,395,395,395,395,395,-1,-1,395,395,395,395,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,395,395,395,395,395,395,395,395,-1,-1,395,395,395,395,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,395,-1,-1,395,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,395,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,395,-1,-1,395,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,395,395,395,395,395,395,395,395,395,395,395,395,395,-1,-1,395,395,395,395,395,395,395,395,395,395,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,395,-1,-1,-1,-1,-1,395,395,395,395,395,395,395,395,395,395,395,395,395,-1,-1,395,395,395,395,395,395,395,395,395,395,395,395,395,395,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
|
||||||
|
|
|
61
camera.py
Normal file
61
camera.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import os
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
from utils.resource_loader import import_assets
|
||||||
|
|
||||||
|
|
||||||
|
class Camera(pygame.sprite.Group):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# General Setup
|
||||||
|
self.display_surface = pygame.display.get_surface()
|
||||||
|
self.half_width = self.display_surface.get_size()[0] // 2
|
||||||
|
self.half_height = self.display_surface.get_size()[1] // 2
|
||||||
|
self.offset = pygame.math.Vector2(100, 200)
|
||||||
|
|
||||||
|
# Creating the floor
|
||||||
|
image_path = import_assets(os.path.join('graphics',
|
||||||
|
'tilemap',
|
||||||
|
'ground.png'))
|
||||||
|
|
||||||
|
self.floor_surf = pygame.image.load(
|
||||||
|
import_assets(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'tilemap',
|
||||||
|
'ground.png')
|
||||||
|
)
|
||||||
|
).convert()
|
||||||
|
|
||||||
|
self.floor_rect = self.floor_surf.get_rect(topleft=(0, 0))
|
||||||
|
|
||||||
|
def custom_draw(self, entity):
|
||||||
|
|
||||||
|
self.sprite_type = entity.sprite_type
|
||||||
|
# Getting the offset
|
||||||
|
if hasattr(entity, 'animation'):
|
||||||
|
self.offset.x = entity.animation.rect.centerx - self.half_width
|
||||||
|
|
||||||
|
self.offset.y = entity.animation.rect.centery - self.half_height
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.offset.x = entity.rect.centerx - self.half_width
|
||||||
|
|
||||||
|
self.offset.y = entity.rect.centery - self.half_height
|
||||||
|
|
||||||
|
# Drawing the floor
|
||||||
|
floor_offset_pos = self.floor_rect.topleft - self.offset
|
||||||
|
self.display_surface.blit(self.floor_surf, floor_offset_pos)
|
||||||
|
|
||||||
|
for sprite in sorted(self.sprites(),
|
||||||
|
key=lambda sprite: sprite.animation.rect.centery
|
||||||
|
if hasattr(sprite, 'animation')
|
||||||
|
else sprite.rect.centery):
|
||||||
|
|
||||||
|
if hasattr(sprite, 'animation'):
|
||||||
|
offset_pos = sprite.animation.rect.topleft - self.offset
|
||||||
|
self.display_surface.blit(sprite.animation.image, offset_pos)
|
||||||
|
else:
|
||||||
|
offset_pos = sprite.rect.topleft - self.offset
|
||||||
|
self.display_surface.blit(sprite.image, offset_pos)
|
|
@ -1,12 +1,41 @@
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '../..', 'assets')
|
|
||||||
|
|
||||||
monster_data = {
|
monster_data = {
|
||||||
'squid': {'id': 1, 'health': .1, 'exp': 1, 'attack': .5, 'attack_type': 'slash', 'speed': 3, 'knockback': 20, 'attack_radius': 80, 'notice_radius': 360},
|
'squid': {'id': 1,
|
||||||
'raccoon': {'id': 2, 'health': .3, 'exp': 2.5, 'attack': .8, 'attack_type': 'claw', 'speed': 2, 'knockback': 20, 'attack_radius': 120, 'notice_radius': 400},
|
'health': .1,
|
||||||
'spirit': {'id': 3, 'health': .1, 'exp': 1.1, 'attack': .6, 'attack_type': 'thunder', 'speed': 4, 'knockback': 20, 'attack_radius': 60, 'notice_radius': 350},
|
'exp': 1,
|
||||||
'bamboo': {'id': 4, 'health': .07, 'exp': 1.2, 'attack': .2, 'attack_type': 'leaf_attack', 'speed': 3, 'knockback': 20, 'attack_radius': 50, 'notice_radius': 300}}
|
'attack': .5,
|
||||||
|
'attack_type': 'slash',
|
||||||
|
'speed': 3,
|
||||||
|
'knockback': 20,
|
||||||
|
'attack_radius': 80,
|
||||||
|
'notice_radius': 360},
|
||||||
|
|
||||||
|
'raccoon': {'id': 2,
|
||||||
|
'health': .3,
|
||||||
|
'exp': 2.5,
|
||||||
|
'attack': .8,
|
||||||
|
'attack_type': 'claw',
|
||||||
|
'speed': 2,
|
||||||
|
'knockback': 20,
|
||||||
|
'attack_radius': 120,
|
||||||
|
'notice_radius': 400},
|
||||||
|
|
||||||
|
'spirit': {'id': 3,
|
||||||
|
'health': .1,
|
||||||
|
'exp': 1.1,
|
||||||
|
'attack': .6,
|
||||||
|
'attack_type': 'thunder',
|
||||||
|
'speed': 4,
|
||||||
|
'knockback': 20,
|
||||||
|
'attack_radius': 60,
|
||||||
|
'notice_radius': 350},
|
||||||
|
|
||||||
|
'bamboo': {'id': 4,
|
||||||
|
'health': .07,
|
||||||
|
'exp': 1.2,
|
||||||
|
'attack': .2,
|
||||||
|
'attack_type': 'leaf_attack',
|
||||||
|
'speed': 3,
|
||||||
|
'knockback': 20,
|
||||||
|
'attack_radius': 50,
|
||||||
|
'notice_radius': 300}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,22 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
from utils.resource_loader import import_assets
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '../..', 'assets')
|
|
||||||
|
|
||||||
magic_data = {
|
magic_data = {
|
||||||
'flame': {'strength': 5, 'cost': .020, 'graphic': f"{asset_path}/graphics/particles/flame/fire.png"},
|
'flame': {'strength': 5, 'cost': .020, 'graphic': import_assets(
|
||||||
'heal': {'strength': 20, 'cost': .010, 'graphic': f"{asset_path}/graphics/particles/heal/heal.png"}}
|
os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'flame',
|
||||||
|
'fire.png')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
'heal': {'strength': 20, 'cost': .010, 'graphic': import_assets(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'heal',
|
||||||
|
'heal.png')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,43 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
from utils.resource_loader import import_assets
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '../..', 'assets')
|
|
||||||
|
|
||||||
weapon_data = {
|
weapon_data = {
|
||||||
'sword': {'cooldown': 100, 'damage': 15, 'graphic': f"{asset_path}/graphics/weapons/sword/full.png"},
|
'sword': {'cooldown': 100, 'damage': 15, 'graphic': import_assets(
|
||||||
'lance': {'cooldown': 400, 'damage': 30, 'graphic': f"{asset_path}/graphics/weapons/lance/full.png"},
|
os.path.join('graphics',
|
||||||
'axe': {'cooldown': 300, 'damage': 20, 'graphic': f"{asset_path}/graphics/weapons/axe/full.png"},
|
'weapons',
|
||||||
'rapier': {'cooldown': 50, 'damage': 8, 'graphic': f"{asset_path}/graphics/weapons/rapier/full.png"},
|
'sword',
|
||||||
'sai': {'cooldown': 80, 'damage': 10, 'graphic': f"{asset_path}/graphics/weapons/sai/full.png"}
|
'full.png')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
'lance': {'cooldown': 400, 'damage': 30, 'graphic': import_assets(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'weapons',
|
||||||
|
'lance',
|
||||||
|
'full.png')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'axe': {'cooldown': 300, 'damage': 20, 'graphic': import_assets(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'weapons',
|
||||||
|
'axe',
|
||||||
|
'full.png')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'rapier': {'cooldown': 50, 'damage': 8, 'graphic': import_assets(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'weapons',
|
||||||
|
'rapier',
|
||||||
|
'full.png')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'sai': {'cooldown': 80, 'damage': 10, 'graphic': import_assets(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'weapons',
|
||||||
|
'sai',
|
||||||
|
'full.png')
|
||||||
|
)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import os
|
|
||||||
import pygame
|
import pygame
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
|
@ -17,9 +16,14 @@ class MagicPlayer:
|
||||||
if player.stats.health >= player.stats.stats['health']:
|
if player.stats.health >= player.stats.stats['health']:
|
||||||
player.stats.health = player.stats.stats['health']
|
player.stats.health = player.stats.stats['health']
|
||||||
self.animation_player.generate_particles(
|
self.animation_player.generate_particles(
|
||||||
'aura', player.rect.center, groups)
|
'aura',
|
||||||
|
player.rect.center,
|
||||||
|
groups)
|
||||||
|
|
||||||
self.animation_player.generate_particles(
|
self.animation_player.generate_particles(
|
||||||
'heal', player.rect.center + pygame.math.Vector2(0, -50), groups)
|
'heal',
|
||||||
|
player.rect.center + pygame.math.Vector2(0, -50),
|
||||||
|
groups)
|
||||||
|
|
||||||
def flame(self, player, cost, groups):
|
def flame(self, player, cost, groups):
|
||||||
if player.stats.energy >= cost:
|
if player.stats.energy >= cost:
|
||||||
|
|
|
@ -8,49 +8,118 @@ from random import choice
|
||||||
class AnimationPlayer:
|
class AnimationPlayer:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '..', 'assets')
|
|
||||||
|
|
||||||
self.frames = {
|
self.frames = {
|
||||||
# magic
|
# Spells
|
||||||
'flame': import_folder(f'{asset_path}/graphics/particles/flame/frames'),
|
'flame': import_folder(os.path.join('graphics',
|
||||||
'aura': import_folder(f'{asset_path}/graphics/particles/aura'),
|
'particles',
|
||||||
'heal': import_folder(f'{asset_path}/graphics/particles/heal/frames'),
|
'flame',
|
||||||
|
'frames')),
|
||||||
|
|
||||||
# attacks
|
'aura': import_folder(os.path.join('graphics',
|
||||||
'claw': import_folder(f'{asset_path}/graphics/particles/claw'),
|
'particles',
|
||||||
'slash': import_folder(f'{asset_path}/graphics/particles/slash'),
|
'aura')),
|
||||||
'sparkle': import_folder(f'{asset_path}/graphics/particles/sparkle'),
|
|
||||||
'leaf_attack': import_folder(f'{asset_path}/graphics/particles/leaf_attack'),
|
|
||||||
'thunder': import_folder(f'{asset_path}/graphics/particles/thunder'),
|
|
||||||
|
|
||||||
# monster deaths
|
'heal': import_folder(os.path.join('graphics',
|
||||||
'squid': import_folder(f'{asset_path}/graphics/particles/smoke_orange'),
|
'particles',
|
||||||
'raccoon': import_folder(f'{asset_path}/graphics/particles/raccoon'),
|
'heal',
|
||||||
'spirit': import_folder(f'{asset_path}/graphics/particles/nova'),
|
'frames')),
|
||||||
'bamboo': import_folder(f'{asset_path}/graphics/particles/bamboo'),
|
|
||||||
|
|
||||||
# leafs
|
# Attacks
|
||||||
|
'claw': import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'claw')),
|
||||||
|
|
||||||
|
'slash': import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'slash')),
|
||||||
|
|
||||||
|
'sparkle': import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'sparkle')),
|
||||||
|
|
||||||
|
'leaf_attack': import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'leaf_attack')),
|
||||||
|
'thunder': import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'thunder')),
|
||||||
|
|
||||||
|
# Monster Deaths
|
||||||
|
'squid': import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'smoke_orange')),
|
||||||
|
|
||||||
|
'raccoon': import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'raccoon')),
|
||||||
|
|
||||||
|
'spirit': import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'nova')),
|
||||||
|
|
||||||
|
'bamboo': import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'bamboo')),
|
||||||
|
|
||||||
|
# Leafs
|
||||||
'leaf': (
|
'leaf': (
|
||||||
import_folder(f'{asset_path}/graphics/particles/leaf1'),
|
import_folder(os.path.join('graphics',
|
||||||
import_folder(f'{asset_path}/graphics/particles/leaf2'),
|
'particles',
|
||||||
import_folder(f'{asset_path}/graphics/particles/leaf3'),
|
'leaf1')),
|
||||||
import_folder(f'{asset_path}/graphics/particles/leaf4'),
|
|
||||||
import_folder(f'{asset_path}/graphics/particles/leaf5'),
|
import_folder(os.path.join('graphics',
|
||||||
import_folder(f'{asset_path}/graphics/particles/leaf6'),
|
'particles',
|
||||||
self.reflect_images(import_folder(
|
'leaf2')),
|
||||||
f'{asset_path}/graphics/particles/leaf1')),
|
|
||||||
self.reflect_images(import_folder(
|
import_folder(os.path.join('graphics',
|
||||||
f'{asset_path}/graphics/particles/leaf2')),
|
'particles',
|
||||||
self.reflect_images(import_folder(
|
'leaf3')),
|
||||||
f'{asset_path}/graphics/particles/leaf3')),
|
|
||||||
self.reflect_images(import_folder(
|
import_folder(os.path.join('graphics',
|
||||||
f'{asset_path}/graphics/particles/leaf4')),
|
'particles',
|
||||||
self.reflect_images(import_folder(
|
'leaf4')),
|
||||||
f'{asset_path}/graphics/particles/leaf5')),
|
|
||||||
self.reflect_images(import_folder(
|
import_folder(os.path.join('graphics',
|
||||||
f'{asset_path}/graphics/particles/leaf6'))
|
'particles',
|
||||||
|
'leaf5')),
|
||||||
|
|
||||||
|
import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'leaf6')),
|
||||||
|
|
||||||
|
self.reflect_images(
|
||||||
|
import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'leaf1'))),
|
||||||
|
|
||||||
|
self.reflect_images(
|
||||||
|
import_folder(os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'leaf2'))),
|
||||||
|
|
||||||
|
self.reflect_images(
|
||||||
|
import_folder(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'leaf3'))),
|
||||||
|
|
||||||
|
self.reflect_images(
|
||||||
|
import_folder(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'leaf4'))),
|
||||||
|
|
||||||
|
self.reflect_images(
|
||||||
|
import_folder(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'leaf5'))),
|
||||||
|
|
||||||
|
self.reflect_images(
|
||||||
|
import_folder(
|
||||||
|
os.path.join('graphics',
|
||||||
|
'particles',
|
||||||
|
'leaf6')))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
import os
|
import os
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
|
from utils.resource_loader import import_assets
|
||||||
|
|
||||||
|
|
||||||
class Weapon(pygame.sprite.Sprite):
|
class Weapon(pygame.sprite.Sprite):
|
||||||
|
|
||||||
def __init__(self, player, groups):
|
def __init__(self, player, groups):
|
||||||
super().__init__(groups)
|
super().__init__(groups)
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '..', 'assets')
|
|
||||||
|
|
||||||
self.sprite_type = 'weapon'
|
self.sprite_type = 'weapon'
|
||||||
direction = player._input.status.split('_')[0]
|
direction = player._input.status.split('_')[0]
|
||||||
|
|
||||||
# Graphic
|
# Graphic
|
||||||
full_path = f"{asset_path}/graphics/weapons/{player._input.combat.weapon}/{direction}.png"
|
self.image = pygame.image.load(import_assets(os.path.join(
|
||||||
self.image = pygame.image.load(full_path).convert_alpha()
|
'graphics',
|
||||||
|
'weapons',
|
||||||
|
player._input.combat.weapon,
|
||||||
|
f"{direction}.png"))
|
||||||
|
).convert_alpha()
|
||||||
|
|
||||||
# Sprite Placement
|
# Sprite Placement
|
||||||
if direction == 'right':
|
if direction == 'right':
|
||||||
|
|
|
@ -38,9 +38,13 @@ class InputHandler:
|
||||||
self.possible_actions = [0, 1, 2, 3, 4, 5]
|
self.possible_actions = [0, 1, 2, 3, 4, 5]
|
||||||
self.action = 10
|
self.action = 10
|
||||||
|
|
||||||
def check_input(self, button, speed, hitbox, obstacle_sprites, rect, player):
|
def check_input(self,
|
||||||
|
button,
|
||||||
self.action = 10
|
speed,
|
||||||
|
hitbox,
|
||||||
|
obstacle_sprites,
|
||||||
|
rect,
|
||||||
|
player):
|
||||||
|
|
||||||
if not self.attacking and self.can_move:
|
if not self.attacking and self.can_move:
|
||||||
|
|
||||||
|
@ -105,9 +109,13 @@ class InputHandler:
|
||||||
|
|
||||||
# Rotating Weapons
|
# Rotating Weapons
|
||||||
if button == 6 and self.can_rotate_weapon:
|
if button == 6 and self.can_rotate_weapon:
|
||||||
|
|
||||||
self.can_rotate_weapon = False
|
self.can_rotate_weapon = False
|
||||||
self.weapon_rotation_time = pygame.time.get_ticks()
|
self.weapon_rotation_time = pygame.time.get_ticks()
|
||||||
if self.combat.weapon_index < len(list(weapon_data.keys())) - 1:
|
|
||||||
|
if self.combat.weapon_index\
|
||||||
|
< len(list(weapon_data.keys())) - 1:
|
||||||
|
|
||||||
self.combat.weapon_index += 1
|
self.combat.weapon_index += 1
|
||||||
else:
|
else:
|
||||||
self.combat.weapon_index = 0
|
self.combat.weapon_index = 0
|
||||||
|
@ -131,23 +139,34 @@ class InputHandler:
|
||||||
self.vulnerable = vulnerable
|
self.vulnerable = vulnerable
|
||||||
|
|
||||||
if self.attacking:
|
if self.attacking:
|
||||||
if current_time - self.attack_time > self.attack_cooldown + weapon_data[self.combat.weapon]['cooldown']:
|
if current_time - self.attack_time\
|
||||||
|
> self.attack_cooldown\
|
||||||
|
+ weapon_data[self.combat.weapon]['cooldown']:
|
||||||
|
|
||||||
self.attacking = False
|
self.attacking = False
|
||||||
if self.combat.current_attack:
|
if self.combat.current_attack:
|
||||||
self.combat.delete_attack_sprite()
|
self.combat.delete_attack_sprite()
|
||||||
|
|
||||||
if not self.can_rotate_weapon:
|
if not self.can_rotate_weapon:
|
||||||
if current_time - self.weapon_rotation_time > self.rotate_attack_cooldown:
|
if current_time - self.weapon_rotation_time\
|
||||||
|
> self.rotate_attack_cooldown:
|
||||||
|
|
||||||
self.can_rotate_weapon = True
|
self.can_rotate_weapon = True
|
||||||
|
|
||||||
if not self.can_swap_magic:
|
if not self.can_swap_magic:
|
||||||
if current_time - self.magic_swap_time > self.rotate_attack_cooldown:
|
if current_time - self.magic_swap_time\
|
||||||
|
> self.rotate_attack_cooldown:
|
||||||
|
|
||||||
self.can_swap_magic = True
|
self.can_swap_magic = True
|
||||||
|
|
||||||
if not vulnerable:
|
if not vulnerable:
|
||||||
if current_time - self.combat.hurt_time >= self.combat.invulnerability_duration:
|
if current_time - self.combat.hurt_time\
|
||||||
|
>= self.combat.invulnerability_duration:
|
||||||
|
|
||||||
self.combat.vulnerable = True
|
self.combat.vulnerable = True
|
||||||
|
|
||||||
if not self.can_move:
|
if not self.can_move:
|
||||||
if current_time - self.move_time >= self.move_cooldown:
|
if current_time - self.move_time\
|
||||||
|
>= self.move_cooldown:
|
||||||
|
|
||||||
self.can_move = True
|
self.can_move = True
|
||||||
|
|
|
@ -2,7 +2,7 @@ import os
|
||||||
import pygame
|
import pygame
|
||||||
from math import sin
|
from math import sin
|
||||||
|
|
||||||
from utils.resource_loader import import_folder
|
from utils.resource_loader import import_folder, import_assets
|
||||||
|
|
||||||
from configs.system.window_config import HITBOX_OFFSET
|
from configs.system.window_config import HITBOX_OFFSET
|
||||||
|
|
||||||
|
@ -17,40 +17,47 @@ class AnimationHandler:
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def import_assets(self, position):
|
def import_assets(self, position):
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '../..', 'assets', 'graphics')
|
|
||||||
|
|
||||||
if self.sprite_type == 'player':
|
|
||||||
|
|
||||||
character_path = f"{asset_path}/player"
|
|
||||||
|
|
||||||
# Import Graphic Assets
|
# Import Graphic Assets
|
||||||
|
|
||||||
|
if self.sprite_type == 'player':
|
||||||
self.image = pygame.image.load(
|
self.image = pygame.image.load(
|
||||||
f"{character_path}/down/down_0.png").convert_alpha()
|
import_assets(os.path.join('graphics',
|
||||||
|
'player',
|
||||||
|
'down',
|
||||||
|
'down_0.png'))).convert_alpha()
|
||||||
|
|
||||||
self.rect = self.image.get_rect(topleft=position)
|
self.rect = self.image.get_rect(topleft=position)
|
||||||
self.hitbox = self.rect.inflate(HITBOX_OFFSET[self.sprite_type])
|
self.hitbox = self.rect.inflate(HITBOX_OFFSET[self.sprite_type])
|
||||||
|
|
||||||
self.animations = {
|
self.animations = {
|
||||||
'up': [], 'down': [], 'left': [], 'right': [],
|
'up': [], 'down': [],
|
||||||
'up_idle': [], 'down_idle': [], 'left_idle': [], 'right_idle': [],
|
'left': [], 'right': [],
|
||||||
'up_attack': [], 'down_attack': [], 'left_attack': [], 'right_attack': []
|
'up_idle': [], 'down_idle': [],
|
||||||
|
'left_idle': [], 'right_idle': [],
|
||||||
|
'up_attack': [], 'down_attack': [],
|
||||||
|
'left_attack': [], 'right_attack': []
|
||||||
}
|
}
|
||||||
|
|
||||||
for animation in self.animations.keys():
|
for animation in self.animations.keys():
|
||||||
full_path = f"{character_path}/{animation}"
|
self.animations[animation]\
|
||||||
self.animations[animation] = import_folder(full_path)
|
= import_folder(os.path.join('graphics',
|
||||||
|
'player',
|
||||||
|
animation
|
||||||
|
))
|
||||||
|
|
||||||
elif self.sprite_type == 'enemy':
|
elif self.sprite_type == 'enemy':
|
||||||
|
|
||||||
self.status = 'idle'
|
self.status = 'idle'
|
||||||
|
|
||||||
character_path = f"{asset_path}/monsters/{self.name}"
|
|
||||||
|
|
||||||
self.animations = {'idle': [], 'move': [], 'attack': []}
|
self.animations = {'idle': [], 'move': [], 'attack': []}
|
||||||
|
|
||||||
for animation in self.animations.keys():
|
for animation in self.animations.keys():
|
||||||
self.animations[animation] = import_folder(
|
self.animations[animation]\
|
||||||
f"{character_path}/{animation}")
|
= import_folder(os.path.join('graphics',
|
||||||
|
'monsters',
|
||||||
|
self.name,
|
||||||
|
animation))
|
||||||
|
|
||||||
self.image = self.animations[self.status][self.frame_index]
|
self.image = self.animations[self.status][self.frame_index]
|
||||||
self.rect = self.image.get_rect(topleft=position)
|
self.rect = self.image.get_rect(topleft=position)
|
||||||
|
|
|
@ -11,11 +11,12 @@ class Enemy(pygame.sprite.Sprite):
|
||||||
|
|
||||||
def __init__(self, name, position, groups, visible_sprites, obstacle_sprites):
|
def __init__(self, name, position, groups, visible_sprites, obstacle_sprites):
|
||||||
super().__init__(groups)
|
super().__init__(groups)
|
||||||
|
|
||||||
self.sprite_type = "enemy"
|
self.sprite_type = "enemy"
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
self.visible_sprites = visible_sprites
|
self.visible_sprites = visible_sprites
|
||||||
|
|
||||||
self.position = position
|
|
||||||
# Setup Graphics
|
# Setup Graphics
|
||||||
self.animation_player = AnimationPlayer()
|
self.animation_player = AnimationPlayer()
|
||||||
self.animation = AnimationHandler(self.sprite_type, self.name)
|
self.animation = AnimationHandler(self.sprite_type, self.name)
|
||||||
|
|
|
@ -3,6 +3,8 @@ import pygame
|
||||||
|
|
||||||
from configs.system.window_config import HITBOX_OFFSET
|
from configs.system.window_config import HITBOX_OFFSET
|
||||||
|
|
||||||
|
from utils.resource_loader import import_assets
|
||||||
|
|
||||||
|
|
||||||
class Observer(pygame.sprite.Sprite):
|
class Observer(pygame.sprite.Sprite):
|
||||||
|
|
||||||
|
@ -11,12 +13,11 @@ class Observer(pygame.sprite.Sprite):
|
||||||
|
|
||||||
self.sprite_type = 'camera'
|
self.sprite_type = 'camera'
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '..', 'assets')
|
|
||||||
|
|
||||||
self.image = pygame.image.load(
|
self.image = pygame.image.load(
|
||||||
f"{asset_path}/graphics/observer.png").convert_alpha()
|
import_assets(os.path.join('graphics',
|
||||||
|
'observer.png'))
|
||||||
|
).convert_alpha()
|
||||||
|
|
||||||
self.rect = self.image.get_rect(topleft=position)
|
self.rect = self.image.get_rect(topleft=position)
|
||||||
self.hitbox = self.rect.inflate(HITBOX_OFFSET[self.sprite_type])
|
self.hitbox = self.rect.inflate(HITBOX_OFFSET[self.sprite_type])
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import os
|
||||||
import pygame
|
import pygame
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from random import randint
|
from random import randint
|
||||||
|
@ -15,66 +16,101 @@ from agents.ppo.agent import Agent
|
||||||
|
|
||||||
|
|
||||||
class Player(pygame.sprite.Sprite):
|
class Player(pygame.sprite.Sprite):
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
player_id,
|
||||||
|
role,
|
||||||
position,
|
position,
|
||||||
groups,
|
groups,
|
||||||
obstacle_sprites,
|
obstacle_sprites,
|
||||||
visible_sprites,
|
visible_sprites,
|
||||||
attack_sprites,
|
attack_sprites,
|
||||||
attackable_sprites,
|
attackable_sprites
|
||||||
role,
|
):
|
||||||
player_id):
|
|
||||||
|
|
||||||
super().__init__(groups)
|
super().__init__(groups)
|
||||||
|
|
||||||
# Setup Sprites
|
self.initial_position = position
|
||||||
self.sprite_type = 'player'
|
|
||||||
self.status = 'down'
|
|
||||||
self.player_id = player_id
|
self.player_id = player_id
|
||||||
|
self.distance_direction_from_enemy = None
|
||||||
|
|
||||||
|
# Sprite Setup
|
||||||
|
self.sprite_type = "player"
|
||||||
|
self.obstacle_sprites = obstacle_sprites
|
||||||
self.visible_sprites = visible_sprites
|
self.visible_sprites = visible_sprites
|
||||||
self.attack_sprites = attack_sprites
|
self.attack_sprites = attack_sprites
|
||||||
self.obstacle_sprites = obstacle_sprites
|
|
||||||
self.attackable_sprites = attackable_sprites
|
self.attackable_sprites = attackable_sprites
|
||||||
|
|
||||||
# Setup Graphics
|
# Graphics Setup
|
||||||
self.animation_player = AnimationPlayer()
|
self.animation_player = AnimationPlayer()
|
||||||
self.animation = AnimationHandler(self.sprite_type)
|
self.animation = AnimationHandler(self.sprite_type)
|
||||||
self.animation.import_assets(position)
|
self.animation.import_assets(position)
|
||||||
self.image = self.animation.image
|
# Input Setup
|
||||||
self.rect = self.animation.rect
|
|
||||||
# Setup Inputs
|
|
||||||
self._input = InputHandler(
|
self._input = InputHandler(
|
||||||
self.sprite_type, self.animation_player) # , self.status)
|
self.sprite_type, self.animation_player)
|
||||||
|
|
||||||
# Setup Stats
|
# Setup Stats
|
||||||
self.role = role
|
self.role = role
|
||||||
self.stats = StatsHandler(self.sprite_type, self.role)
|
self.stats = StatsHandler(self.sprite_type, self.role)
|
||||||
|
|
||||||
self.distance_direction_from_enemy = None
|
def setup_agent(self,
|
||||||
|
gamma,
|
||||||
|
alpha,
|
||||||
|
policy_clip,
|
||||||
|
batch_size,
|
||||||
|
N,
|
||||||
|
n_epochs,
|
||||||
|
gae_lambda,
|
||||||
|
chkpt_dir,
|
||||||
|
no_load=False):
|
||||||
|
|
||||||
# Setup AI
|
self.get_current_state()
|
||||||
self.score = 0
|
self.agent = Agent(
|
||||||
self.learn_iters = 0
|
input_dims=len(self.state_features),
|
||||||
self.n_steps = 0
|
n_actions=len(self._input.possible_actions),
|
||||||
self.N = 20
|
gamma=gamma,
|
||||||
|
alpha=alpha,
|
||||||
|
policy_clip=policy_clip,
|
||||||
|
batch_size=batch_size,
|
||||||
|
N=N,
|
||||||
|
n_epochs=n_epochs,
|
||||||
|
gae_lambda=gae_lambda,
|
||||||
|
chkpt_dir=chkpt_dir
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"\nAgent initialized on player {self.player_id} using {self.agent.actor.device}.")
|
||||||
|
|
||||||
|
if not no_load:
|
||||||
|
print("Attempting to load models ...")
|
||||||
|
try:
|
||||||
|
self.agent.load_models(
|
||||||
|
actr_chkpt=f"A{self.player_id}",
|
||||||
|
crtc_chkpt=f"C{self.player_id}"
|
||||||
|
)
|
||||||
|
print("Models loaded ...\n")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(
|
||||||
|
f"FileNotFound for player {self.player_id}.\
|
||||||
|
\nSkipping loading ...\n")
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
if self._input.movement.direction.x == 0 and self._input.movement.direction.y == 0:
|
if self._input.movement.direction.x == 0\
|
||||||
if 'idle' not in self.status and 'attack' not in self.status:
|
and self._input.movement.direction.y == 0:
|
||||||
self.status += '_idle'
|
|
||||||
|
if 'idle' not in self._input.status and 'attack' not in self._input.status:
|
||||||
|
self._input.status += '_idle'
|
||||||
|
|
||||||
if self._input.attacking:
|
if self._input.attacking:
|
||||||
self._input.movement.direction.x = 0
|
self._input.movement.direction.x = 0
|
||||||
self._input.movement.direction.y = 0
|
self._input.movement.direction.y = 0
|
||||||
if 'attack' not in self.status:
|
if 'attack' not in self._input.status:
|
||||||
if 'idle' in self.status:
|
if 'idle' in self._input.status:
|
||||||
self.status = self.status.replace('idle', 'attack')
|
self._input.status = self._input.status.replace(
|
||||||
|
'idle', 'attack')
|
||||||
else:
|
else:
|
||||||
self.status += '_attack'
|
self._input.status += '_attack'
|
||||||
else:
|
else:
|
||||||
if 'attack' in self.status:
|
if 'attack' in self._input.status:
|
||||||
self.status = self.status.replace('_attack', '')
|
self._input.status = self._input.status.replace('_attack', '')
|
||||||
|
|
||||||
def attack_logic(self):
|
def attack_logic(self):
|
||||||
if self.attack_sprites:
|
if self.attack_sprites:
|
||||||
|
@ -123,11 +159,12 @@ class Player(pygame.sprite.Sprite):
|
||||||
2*np.exp(-nearest_dist**2),
|
2*np.exp(-nearest_dist**2),
|
||||||
np.exp(-nearest_enemy.stats.health),
|
np.exp(-nearest_enemy.stats.health),
|
||||||
-np.exp(-self.stats.health**2)
|
-np.exp(-self.stats.health**2)
|
||||||
|
if not self.is_dead() > 0 else -1
|
||||||
]
|
]
|
||||||
|
|
||||||
self.state_features = [
|
self.state_features = [
|
||||||
np.exp(-self.rect.center[0]),
|
np.exp(-self.animation.rect.center[0]),
|
||||||
np.exp(-self.rect.center[1]),
|
np.exp(-self.animation.rect.center[1]),
|
||||||
self._input.movement.direction.x,
|
self._input.movement.direction.x,
|
||||||
self._input.movement.direction.y,
|
self._input.movement.direction.y,
|
||||||
self.stats.health/self.stats.stats['health'],
|
self.stats.health/self.stats.stats['health'],
|
||||||
|
@ -153,42 +190,23 @@ class Player(pygame.sprite.Sprite):
|
||||||
|
|
||||||
self.state_features = np.array(self.state_features)
|
self.state_features = np.array(self.state_features)
|
||||||
|
|
||||||
def get_max_num_states(self):
|
|
||||||
self.get_current_state()
|
|
||||||
self.num_features = len(self.state_features)
|
|
||||||
|
|
||||||
def setup_agent(self):
|
|
||||||
print(f"Initializing agent on player {self.player_id} ...")
|
|
||||||
self.agent = Agent(
|
|
||||||
input_dims=len(self.state_features),
|
|
||||||
n_actions=len(self._input.possible_actions),
|
|
||||||
batch_size=5,
|
|
||||||
n_epochs=4)
|
|
||||||
print(
|
|
||||||
f" Agent initialized using {self.agent.actor.device}. Attempting to load models ...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.agent.load_models(
|
|
||||||
actr_chkpt=f"player_actor", crtc_chkpt=f"player_critic")
|
|
||||||
print("Models loaded ...\n")
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
print("FileNotFound for agent. Skipping loading...\n")
|
|
||||||
|
|
||||||
def is_dead(self):
|
def is_dead(self):
|
||||||
if self.stats.health <= 0:
|
if self.stats.health <= 0:
|
||||||
|
self.stats.health = 0
|
||||||
|
self.animation.import_assets((3264, 448))
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
|
|
||||||
|
if not self.is_dead():
|
||||||
# Get the current state
|
# Get the current state
|
||||||
self.get_current_state()
|
self.get_current_state()
|
||||||
# Choose action based on current state
|
# Choose action based on current state
|
||||||
action, probs, value = self.agent.choose_action(self.state_features)
|
action, probs, value\
|
||||||
|
= self.agent.choose_action(self.state_features)
|
||||||
|
|
||||||
self.n_steps += 1
|
|
||||||
# Apply chosen action
|
# Apply chosen action
|
||||||
self._input.check_input(action,
|
self._input.check_input(action,
|
||||||
self.stats.speed,
|
self.stats.speed,
|
||||||
|
@ -201,25 +219,19 @@ class Player(pygame.sprite.Sprite):
|
||||||
self.agent.remember(self.state_features, action,
|
self.agent.remember(self.state_features, action,
|
||||||
probs, value, self.stats.exp, self.is_dead())
|
probs, value, self.stats.exp, self.is_dead())
|
||||||
|
|
||||||
if self.n_steps % self.N == 0:
|
|
||||||
self.agent.learn()
|
|
||||||
self.learn_iters += 1
|
|
||||||
|
|
||||||
self.get_current_state()
|
self.get_current_state()
|
||||||
|
|
||||||
# Refresh objects based on input
|
|
||||||
self.status = self._input.status
|
|
||||||
|
|
||||||
# Animate
|
|
||||||
self.get_status()
|
|
||||||
self.animation.animate(self.status, self._input.combat.vulnerable)
|
|
||||||
self.image = self.animation.image
|
|
||||||
self.rect = self.animation.rect
|
|
||||||
|
|
||||||
# Cooldowns and Regen
|
# Cooldowns and Regen
|
||||||
self.stats.health_recovery()
|
self.stats.health_recovery()
|
||||||
self.stats.energy_recovery()
|
self.stats.energy_recovery()
|
||||||
self._input.cooldowns(self._input.combat.vulnerable)
|
|
||||||
|
|
||||||
if self.is_dead():
|
else:
|
||||||
self.stats.exp = max(-1, self.stats.exp - .5)
|
self.stats.exp = max(0, self.stats.exp - .01)
|
||||||
|
|
||||||
|
# Refresh player based on input and animate
|
||||||
|
self.get_status()
|
||||||
|
self.animation.animate(
|
||||||
|
self._input.status, self._input.combat.vulnerable)
|
||||||
|
self.image = self.animation.image
|
||||||
|
self.rect = self.animation.rect
|
||||||
|
self._input.cooldowns(self._input.combat.vulnerable)
|
||||||
|
|
|
@ -1,15 +1,26 @@
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from configs.system.window_config import *
|
from configs.system.window_config import TILESIZE,\
|
||||||
|
HITBOX_OFFSET
|
||||||
|
|
||||||
|
|
||||||
class Tile(pygame.sprite.Sprite):
|
class Terrain(pygame.sprite.Sprite):
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
position,
|
||||||
|
groups,
|
||||||
|
sprite_type,
|
||||||
|
surface=pygame.Surface((TILESIZE, TILESIZE))
|
||||||
|
):
|
||||||
|
|
||||||
def __init__(self, position, groups, sprite_type, surface=pygame.Surface((TILESIZE, TILESIZE))):
|
|
||||||
super().__init__(groups)
|
super().__init__(groups)
|
||||||
|
|
||||||
self.sprite_type = sprite_type
|
self.sprite_type = sprite_type
|
||||||
|
|
||||||
|
self.position = position
|
||||||
|
|
||||||
self.image = surface
|
self.image = surface
|
||||||
|
|
||||||
if sprite_type == 'object':
|
if sprite_type == 'object':
|
||||||
# Offset
|
# Offset
|
||||||
self.rect = self.image.get_rect(
|
self.rect = self.image.get_rect(
|
BIN
figures/actor_loss.png
Normal file
BIN
figures/actor_loss.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
figures/critic_loss.png
Normal file
BIN
figures/critic_loss.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
figures/score.png
Normal file
BIN
figures/score.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
figures/total_loss.png
Normal file
BIN
figures/total_loss.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
51
game.py
51
game.py
|
@ -1,49 +1,58 @@
|
||||||
import pygame
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import pygame
|
||||||
|
|
||||||
from level.level import Level
|
from level import Level
|
||||||
from configs.system.window_config import WIDTH, HEIGHT, WATER_COLOR, FPS
|
|
||||||
|
from configs.system.window_config import WIDTH,\
|
||||||
|
HEIGHT,\
|
||||||
|
WATER_COLOR,\
|
||||||
|
FPS
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
|
|
||||||
def __init__(self, n_players):
|
def __init__(self, show_pg=False, n_players=1,):
|
||||||
|
print(f"Initializing Pneuma with {n_players} player(s).\
|
||||||
|
\nShowing PyGame screen: {'True' if show_pg else 'False'}")
|
||||||
|
|
||||||
pygame.init()
|
pygame.init()
|
||||||
|
|
||||||
|
if show_pg:
|
||||||
|
|
||||||
self.screen = pygame.display.set_mode(
|
self.screen = pygame.display.set_mode(
|
||||||
(WIDTH, HEIGHT)) # , pygame.HIDDEN)
|
(WIDTH, HEIGHT)
|
||||||
|
)
|
||||||
|
|
||||||
pygame.display.set_caption('Pneuma')
|
else:
|
||||||
|
self.screen = pygame.display.set_mode(
|
||||||
|
(WIDTH, HEIGHT),
|
||||||
|
pygame.HIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
img = pygame.image.load('assets/graphics/icon.png')
|
pygame.display.set_caption("Pneuma")
|
||||||
|
|
||||||
|
img = pygame.image.load(os.path.join('assets',
|
||||||
|
'graphics',
|
||||||
|
'icon.png'))
|
||||||
pygame.display.set_icon(img)
|
pygame.display.set_icon(img)
|
||||||
self.clock = pygame.time.Clock()
|
|
||||||
|
|
||||||
self.level = Level(n_players)
|
self.level = Level(n_players)
|
||||||
|
|
||||||
self.max_num_players = len(self.level.player_sprites)
|
|
||||||
|
|
||||||
def calc_score(self):
|
|
||||||
|
|
||||||
self.scores = [0 for _ in range(self.max_num_players)]
|
|
||||||
|
|
||||||
for player in self.level.player_sprites:
|
|
||||||
self.scores[player.player_id] = player.stats.exp
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
|
self.clock = pygame.time.Clock()
|
||||||
|
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
self.quit()
|
self.quit()
|
||||||
if event.type == pygame.KEYDOWN:
|
elif event.type == pygame.KEYDOWN:
|
||||||
if event.key == pygame.K_m:
|
if event.key == pygame.K_m:
|
||||||
self.level.toggle_menu()
|
self.level.pause()
|
||||||
self.level.observer.update()
|
|
||||||
|
|
||||||
self.screen.fill(WATER_COLOR)
|
self.screen.fill(WATER_COLOR)
|
||||||
|
|
||||||
self.level.run()
|
self.level.run('observer', self.clock.get_fps())
|
||||||
|
|
||||||
pygame.display.update()
|
pygame.display.update()
|
||||||
self.clock.tick(FPS)
|
self.clock.tick(FPS)
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from configs.game.weapon_config import *
|
from configs.game.weapon_config import weapon_data
|
||||||
from configs.game.spell_config import *
|
from configs.game.spell_config import magic_data
|
||||||
|
|
||||||
from .ui_settings import *
|
from .ui_settings import UI_FONT,\
|
||||||
|
UI_FONT_SIZE,\
|
||||||
|
HEALTH_BAR_WIDTH,\
|
||||||
|
HEALTH_COLOR,\
|
||||||
|
ENERGY_BAR_WIDTH,\
|
||||||
|
ENERGY_COLOR,\
|
||||||
|
BAR_HEIGHT,\
|
||||||
|
UI_BG_COLOR,\
|
||||||
|
UI_BORDER_COLOR_ACTIVE,\
|
||||||
|
UI_BORDER_COLOR,\
|
||||||
|
TEXT_COLOR,\
|
||||||
|
ITEM_BOX_SIZE
|
||||||
|
|
||||||
|
|
||||||
class UI:
|
class UI:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '../..', 'assets')
|
|
||||||
|
|
||||||
# General info
|
# General info
|
||||||
self.display_surface = pygame.display.get_surface()
|
self.display_surface = pygame.display.get_surface()
|
||||||
self.font = pygame.font.Font(UI_FONT, UI_FONT_SIZE)
|
self.font = pygame.font.Font(UI_FONT, UI_FONT_SIZE)
|
||||||
|
@ -66,7 +73,7 @@ class UI:
|
||||||
pygame.draw.rect(self.display_surface,
|
pygame.draw.rect(self.display_surface,
|
||||||
UI_BORDER_COLOR, text_rect.inflate(10, 10), 4)
|
UI_BORDER_COLOR, text_rect.inflate(10, 10), 4)
|
||||||
else:
|
else:
|
||||||
text_surf = self.font.render(f"OBSERVER", False, TEXT_COLOR)
|
text_surf = self.font.render("OBSERVER", False, TEXT_COLOR)
|
||||||
x = self.display_surface.get_size()[0] - 20
|
x = self.display_surface.get_size()[0] - 20
|
||||||
y = self.display_surface.get_size()[1] - 20
|
y = self.display_surface.get_size()[1] - 20
|
||||||
text_rect = text_surf.get_rect(bottomright=(x, y))
|
text_rect = text_surf.get_rect(bottomright=(x, y))
|
||||||
|
@ -104,9 +111,17 @@ class UI:
|
||||||
def display(self, player):
|
def display(self, player):
|
||||||
if player.sprite_type == 'player':
|
if player.sprite_type == 'player':
|
||||||
self.show_bar(
|
self.show_bar(
|
||||||
player.stats.health, player.stats.stats['health'], self.health_bar_rect, HEALTH_COLOR)
|
player.stats.health,
|
||||||
|
player.stats.stats['health'],
|
||||||
|
self.health_bar_rect,
|
||||||
|
HEALTH_COLOR)
|
||||||
|
|
||||||
self.show_bar(
|
self.show_bar(
|
||||||
player.stats.energy, player.stats.stats['energy'], self.energy_bar_rect, ENERGY_COLOR)
|
player.stats.energy,
|
||||||
|
player.stats.stats['energy'],
|
||||||
|
self.energy_bar_rect,
|
||||||
|
ENERGY_COLOR)
|
||||||
|
|
||||||
self.show_exp(player.stats.exp)
|
self.show_exp(player.stats.exp)
|
||||||
self.weapon_overlay(player._input.combat.weapon_index,
|
self.weapon_overlay(player._input.combat.weapon_index,
|
||||||
player._input.can_rotate_weapon)
|
player._input.can_rotate_weapon)
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
import os
|
import os
|
||||||
|
from utils.resource_loader import import_assets
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
# UI
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '..', 'assets')
|
|
||||||
|
|
||||||
# ui
|
|
||||||
BAR_HEIGHT = 20
|
BAR_HEIGHT = 20
|
||||||
HEALTH_BAR_WIDTH = 200
|
HEALTH_BAR_WIDTH = 200
|
||||||
ENERGY_BAR_WIDTH = 140
|
ENERGY_BAR_WIDTH = 140
|
||||||
ITEM_BOX_SIZE = 80
|
ITEM_BOX_SIZE = 80
|
||||||
UI_FONT = f"{asset_path}/font/joystix.ttf"
|
UI_FONT = import_assets(path=os.path.join('font', 'joystix.ttf'))
|
||||||
UI_FONT_SIZE = 18
|
UI_FONT_SIZE = 18
|
||||||
|
|
||||||
# general colors
|
# General Colors
|
||||||
WATER_COLOR = '#71ddee'
|
WATER_COLOR = '#71ddee'
|
||||||
UI_BG_COLOR = '#222222'
|
UI_BG_COLOR = '#222222'
|
||||||
UI_BORDER_COLOR = '#111111'
|
UI_BORDER_COLOR = '#111111'
|
||||||
TEXT_COLOR = '#EEEEEE'
|
TEXT_COLOR = '#EEEEEE'
|
||||||
|
|
||||||
# ui colors
|
# UI Colors
|
||||||
HEALTH_COLOR = 'red'
|
HEALTH_COLOR = 'red'
|
||||||
ENERGY_COLOR = 'blue'
|
ENERGY_COLOR = 'blue'
|
||||||
UI_BORDER_COLOR_ACTIVE = 'gold'
|
UI_BORDER_COLOR_ACTIVE = 'gold'
|
||||||
|
|
||||||
# Upgrade menu
|
# Upgrade Menu
|
||||||
TEXT_COLOR_SELECTED = '#111111'
|
TEXT_COLOR_SELECTED = '#111111'
|
||||||
BAR_COLOR = '#EEEEEE'
|
BAR_COLOR = '#EEEEEE'
|
||||||
BAR_COLOR_SELECTED = '#111111'
|
BAR_COLOR_SELECTED = '#111111'
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from .ui_settings import UI_FONT, UI_FONT_SIZE, TEXT_COLOR, TEXT_COLOR_SELECTED, UPGRADE_BG_COLOR_SELECTED, UI_BORDER_COLOR, UI_BG_COLOR, BAR_COLOR_SELECTED, BAR_COLOR
|
from .ui_settings import UI_FONT,\
|
||||||
|
UI_FONT_SIZE,\
|
||||||
|
TEXT_COLOR,\
|
||||||
|
TEXT_COLOR_SELECTED,\
|
||||||
|
UPGRADE_BG_COLOR_SELECTED,\
|
||||||
|
UI_BORDER_COLOR,\
|
||||||
|
UI_BG_COLOR,\
|
||||||
|
BAR_COLOR_SELECTED,\
|
||||||
|
BAR_COLOR
|
||||||
|
|
||||||
|
|
||||||
class Upgrade:
|
class Upgrade:
|
||||||
|
|
305
level.py
Normal file
305
level.py
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
import os
|
||||||
|
import pygame
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
from configs.system.window_config import TILESIZE
|
||||||
|
|
||||||
|
from utils.debug import debug
|
||||||
|
from utils.resource_loader import import_csv_layout, import_folder
|
||||||
|
|
||||||
|
from interface.ui import UI
|
||||||
|
|
||||||
|
from entities.observer import Observer
|
||||||
|
from entities.player import Player
|
||||||
|
from entities.enemy import Enemy
|
||||||
|
from entities.terrain import Terrain
|
||||||
|
|
||||||
|
from camera import Camera
|
||||||
|
|
||||||
|
|
||||||
|
class Level:
|
||||||
|
|
||||||
|
def __init__(self, n_players):
|
||||||
|
|
||||||
|
self.paused = False
|
||||||
|
self.done = False
|
||||||
|
|
||||||
|
# Get display surface
|
||||||
|
self.display_surface = pygame.display.get_surface()
|
||||||
|
|
||||||
|
# Setup Sprite groups
|
||||||
|
self.visible_sprites = Camera()
|
||||||
|
self.obstacle_sprites = pygame.sprite.Group()
|
||||||
|
self.attack_sprites = pygame.sprite.Group()
|
||||||
|
self.attackable_sprites = pygame.sprite.Group()
|
||||||
|
|
||||||
|
# Map generation
|
||||||
|
self.n_players = n_players
|
||||||
|
self.generate_map()
|
||||||
|
|
||||||
|
# Handle generated entities
|
||||||
|
self.get_entities()
|
||||||
|
self.get_distance_direction()
|
||||||
|
self.dead_players = np.zeros(self.n_players)
|
||||||
|
|
||||||
|
# Setup UI
|
||||||
|
self.ui = UI()
|
||||||
|
|
||||||
|
def generate_map(self):
|
||||||
|
|
||||||
|
self.possible_player_locations = []
|
||||||
|
|
||||||
|
player_id = 0
|
||||||
|
|
||||||
|
self.layouts = {
|
||||||
|
'boundary': import_csv_layout(os.path.join('map',
|
||||||
|
'FloorBlocks.csv')),
|
||||||
|
'grass': import_csv_layout(os.path.join('map',
|
||||||
|
'Grass.csv')),
|
||||||
|
'objects': import_csv_layout(os.path.join('map',
|
||||||
|
'Objects.csv')),
|
||||||
|
'entities': import_csv_layout(os.path.join('map',
|
||||||
|
'Entities.csv'))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.graphics = {
|
||||||
|
'grass': import_folder(os.path.join('graphics', 'grass')),
|
||||||
|
'objects': import_folder(os.path.join('graphics', 'objects'))
|
||||||
|
}
|
||||||
|
|
||||||
|
for style, layout in self.layouts.items():
|
||||||
|
for row_index, row in enumerate(layout):
|
||||||
|
for col_index, col in enumerate(row):
|
||||||
|
if int(col) != -1:
|
||||||
|
|
||||||
|
x = col_index * TILESIZE
|
||||||
|
y = row_index * TILESIZE
|
||||||
|
|
||||||
|
# Generate unpassable terrain
|
||||||
|
if style == 'boundary':
|
||||||
|
if col != '700':
|
||||||
|
Terrain((x, y),
|
||||||
|
[self.obstacle_sprites,
|
||||||
|
self.visible_sprites],
|
||||||
|
'invisible')
|
||||||
|
if col == '700':
|
||||||
|
print(f"Prison set at:{(x, y)}")
|
||||||
|
# Generate grass
|
||||||
|
if style == 'grass':
|
||||||
|
random_grass_image = choice(self.graphics['grass'])
|
||||||
|
|
||||||
|
Terrain((x, y), [
|
||||||
|
self.visible_sprites,
|
||||||
|
self.obstacle_sprites,
|
||||||
|
self.attackable_sprites
|
||||||
|
],
|
||||||
|
'grass',
|
||||||
|
random_grass_image)
|
||||||
|
|
||||||
|
# Generate objects like trees and statues
|
||||||
|
# if style == 'objects':
|
||||||
|
# surface = self.graphics['objects'][int(col)]
|
||||||
|
# Terrain((x, y), [
|
||||||
|
# self.visible_sprites,
|
||||||
|
# self.obstacle_sprites
|
||||||
|
# ],
|
||||||
|
# 'object',
|
||||||
|
# surface)
|
||||||
|
|
||||||
|
# Generate observer, players and monsters
|
||||||
|
if style == 'entities':
|
||||||
|
|
||||||
|
# Generate observer
|
||||||
|
if col == '500':
|
||||||
|
self.observer = Observer(
|
||||||
|
(x, y),
|
||||||
|
[self.visible_sprites]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate player(s)
|
||||||
|
# TODO: Make a way to generate players in random locations
|
||||||
|
elif col == '400':
|
||||||
|
self.possible_player_locations.append((x, y))
|
||||||
|
# Monster generation
|
||||||
|
|
||||||
|
else:
|
||||||
|
if col == '390':
|
||||||
|
monster_name = 'bamboo'
|
||||||
|
elif col == '391':
|
||||||
|
monster_name = 'spirit'
|
||||||
|
elif col == '392':
|
||||||
|
monster_name = 'raccoon'
|
||||||
|
elif col == ' 393':
|
||||||
|
monster_name = 'squid'
|
||||||
|
Enemy(name=monster_name,
|
||||||
|
position=(x, y),
|
||||||
|
groups=[self.visible_sprites,
|
||||||
|
self.attackable_sprites],
|
||||||
|
visible_sprites=self.visible_sprites,
|
||||||
|
obstacle_sprites=self.obstacle_sprites)
|
||||||
|
|
||||||
|
for player_id in range(self.n_players):
|
||||||
|
Player(
|
||||||
|
player_id,
|
||||||
|
'tank',
|
||||||
|
choice(self.possible_player_locations),
|
||||||
|
[self.visible_sprites],
|
||||||
|
self.obstacle_sprites,
|
||||||
|
self.visible_sprites,
|
||||||
|
self.attack_sprites,
|
||||||
|
self.attackable_sprites
|
||||||
|
)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
|
||||||
|
for grass in self.grass_sprites:
|
||||||
|
grass.kill()
|
||||||
|
|
||||||
|
for enemy in self.enemy_sprites:
|
||||||
|
enemy.kill()
|
||||||
|
|
||||||
|
for style, layout in self.layouts.items():
|
||||||
|
for row_index, row in enumerate(layout):
|
||||||
|
for col_index, col in enumerate(row):
|
||||||
|
if int(col) != -1:
|
||||||
|
x = col_index * TILESIZE
|
||||||
|
y = row_index * TILESIZE
|
||||||
|
# Regenerate grass
|
||||||
|
if style == 'grass':
|
||||||
|
random_grass_image = choice(
|
||||||
|
self.graphics['grass'])
|
||||||
|
|
||||||
|
Terrain((x, y), [
|
||||||
|
self.visible_sprites,
|
||||||
|
self.obstacle_sprites,
|
||||||
|
self.attackable_sprites
|
||||||
|
],
|
||||||
|
'grass',
|
||||||
|
random_grass_image)
|
||||||
|
|
||||||
|
if style == 'entities':
|
||||||
|
|
||||||
|
if col == '500':
|
||||||
|
continue
|
||||||
|
|
||||||
|
if col == '400':
|
||||||
|
continue
|
||||||
|
|
||||||
|
else:
|
||||||
|
if col == '390':
|
||||||
|
monster_name = 'bamboo'
|
||||||
|
elif col == '391':
|
||||||
|
monster_name = 'spirit'
|
||||||
|
elif col == '392':
|
||||||
|
monster_name = 'raccoon'
|
||||||
|
elif col == ' 393':
|
||||||
|
monster_name = 'squid'
|
||||||
|
|
||||||
|
Enemy(monster_name,
|
||||||
|
(x, y),
|
||||||
|
[self.visible_sprites,
|
||||||
|
self.attackable_sprites],
|
||||||
|
self.visible_sprites,
|
||||||
|
self.obstacle_sprites)
|
||||||
|
|
||||||
|
for player in self.player_sprites:
|
||||||
|
player.animation.import_assets(
|
||||||
|
choice(self.possible_player_locations))
|
||||||
|
player.stats.health\
|
||||||
|
= player.stats.stats['health']
|
||||||
|
player.stats.energy\
|
||||||
|
= player.stats.stats['energy']
|
||||||
|
|
||||||
|
self.get_entities()
|
||||||
|
self.get_distance_direction()
|
||||||
|
self.dead_players = np.zeros(self.n_players)
|
||||||
|
self.done = False
|
||||||
|
|
||||||
|
def get_entities(self):
|
||||||
|
|
||||||
|
self.player_sprites = [sprite
|
||||||
|
for sprite in self.visible_sprites.sprites()
|
||||||
|
if sprite.sprite_type == 'player']
|
||||||
|
|
||||||
|
self.enemy_sprites = [sprite
|
||||||
|
for sprite in self.visible_sprites.sprites()
|
||||||
|
if sprite.sprite_type == 'enemy']
|
||||||
|
|
||||||
|
self.grass_sprites = [sprite
|
||||||
|
for sprite in self.visible_sprites.sprites()
|
||||||
|
if sprite.sprite_type == 'grass']
|
||||||
|
|
||||||
|
def get_distance_direction(self):
|
||||||
|
for player in self.player_sprites:
|
||||||
|
player.distance_direction_from_enemy = []
|
||||||
|
|
||||||
|
for enemy in self.enemy_sprites:
|
||||||
|
enemy.distance_direction_from_player = []
|
||||||
|
|
||||||
|
for player in self.player_sprites:
|
||||||
|
if not player.is_dead():
|
||||||
|
player_vector = pygame.math.Vector2(
|
||||||
|
player.animation.rect.center
|
||||||
|
)
|
||||||
|
|
||||||
|
for enemy in self.enemy_sprites:
|
||||||
|
enemy_vector = pygame.math.Vector2(
|
||||||
|
enemy.animation.rect.center
|
||||||
|
)
|
||||||
|
distance\
|
||||||
|
= (player_vector - enemy_vector).magnitude()
|
||||||
|
|
||||||
|
if distance > 0:
|
||||||
|
direction\
|
||||||
|
= (player_vector - enemy_vector).normalize()
|
||||||
|
else:
|
||||||
|
direction\
|
||||||
|
= pygame.math.Vector2()
|
||||||
|
|
||||||
|
enemy.distance_direction_from_player.append(
|
||||||
|
(distance, direction, player))
|
||||||
|
player.distance_direction_from_enemy.append(
|
||||||
|
(distance, -direction, enemy))
|
||||||
|
|
||||||
|
def apply_damage_to_player(self):
|
||||||
|
for enemy in self.enemy_sprites:
|
||||||
|
for distance, _, player in enemy.distance_direction_from_player:
|
||||||
|
|
||||||
|
if (distance < enemy.stats.attack_radius
|
||||||
|
and player._input.combat.vulnerable):
|
||||||
|
|
||||||
|
player.stats.health -= enemy.stats.attack
|
||||||
|
player._input.combat.vulnerable = False
|
||||||
|
player._input.combat.hurt_time = pygame.time.get_ticks()
|
||||||
|
|
||||||
|
def toggle_pause(self):
|
||||||
|
self.paused = not self.paused
|
||||||
|
|
||||||
|
def run(self, who='observer', fps='v0.9'):
|
||||||
|
# Draw the game
|
||||||
|
self.visible_sprites.custom_draw(self.observer)
|
||||||
|
self.ui.display(self.observer)
|
||||||
|
|
||||||
|
debug(f"{fps}")
|
||||||
|
|
||||||
|
if not self.paused:
|
||||||
|
# Update the game
|
||||||
|
for player in self.player_sprites:
|
||||||
|
if player.stats.health > 0:
|
||||||
|
player.attack_logic()
|
||||||
|
|
||||||
|
self.get_entities()
|
||||||
|
self.get_distance_direction()
|
||||||
|
self.visible_sprites.update()
|
||||||
|
self.apply_damage_to_player()
|
||||||
|
|
||||||
|
else:
|
||||||
|
debug('PAUSED')
|
||||||
|
|
||||||
|
for player in self.player_sprites:
|
||||||
|
self.dead_players[player.player_id] = player.is_dead()
|
||||||
|
|
||||||
|
self.done = True if (self.dead_players.all() == 1
|
||||||
|
or self.enemy_sprites == []) else False
|
|
@ -1,42 +0,0 @@
|
||||||
import os
|
|
||||||
import pygame
|
|
||||||
|
|
||||||
|
|
||||||
class Camera(pygame.sprite.Group):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
# General Setup
|
|
||||||
self.display_surface = pygame.display.get_surface()
|
|
||||||
self.half_width = self.display_surface.get_size()[0] // 2
|
|
||||||
self.half_height = self.display_surface.get_size()[1] // 2
|
|
||||||
self.offset = pygame.math.Vector2(100, 200)
|
|
||||||
|
|
||||||
# Creating the floor
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
image_path = os.path.join(
|
|
||||||
script_dir, '..', 'assets', 'graphics', 'tilemap', 'ground.png')
|
|
||||||
|
|
||||||
self.floor_surf = pygame.image.load(image_path).convert()
|
|
||||||
self.floor_rect = self.floor_surf.get_rect(topleft=(0, 0))
|
|
||||||
|
|
||||||
def custom_draw(self, player):
|
|
||||||
self.sprite_type = player.sprite_type
|
|
||||||
# Getting the offset
|
|
||||||
self.offset.x = player.rect.centerx - self.half_width
|
|
||||||
self.offset.y = player.rect.centery - self.half_height
|
|
||||||
|
|
||||||
# Drawing the floor
|
|
||||||
floor_offset_pos = self.floor_rect.topleft - self.offset
|
|
||||||
self.display_surface.blit(self.floor_surf, floor_offset_pos)
|
|
||||||
|
|
||||||
for sprite in sorted(self.sprites(), key=lambda sprite: sprite.rect.centery):
|
|
||||||
offset_pos = sprite.rect.topleft - self.offset
|
|
||||||
self.display_surface.blit(sprite.image, offset_pos)
|
|
||||||
|
|
||||||
def enemy_update(self, player):
|
|
||||||
enemy_sprites = [sprite for sprite in self.sprites() if hasattr(
|
|
||||||
sprite, 'sprite_type') and sprite.sprite_type == 'enemy']
|
|
||||||
for enemy in enemy_sprites:
|
|
||||||
enemy.enemy_update(player)
|
|
229
level/level.py
229
level/level.py
|
@ -1,229 +0,0 @@
|
||||||
import os
|
|
||||||
import pygame
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from random import choice
|
|
||||||
|
|
||||||
from configs.system.window_config import TILESIZE
|
|
||||||
|
|
||||||
from utils.debug import debug
|
|
||||||
from utils.resource_loader import import_csv_layout, import_folder
|
|
||||||
|
|
||||||
from interface.ui import UI
|
|
||||||
|
|
||||||
from entities.observer import Observer
|
|
||||||
from entities.player import Player
|
|
||||||
from entities.enemy import Enemy
|
|
||||||
|
|
||||||
from .terrain import Tile
|
|
||||||
from .camera import Camera
|
|
||||||
|
|
||||||
|
|
||||||
class Level:
|
|
||||||
|
|
||||||
def __init__(self, n_players, reset=False):
|
|
||||||
|
|
||||||
# General Settings
|
|
||||||
self.game_paused = False
|
|
||||||
self.done = False
|
|
||||||
|
|
||||||
# Get display surface
|
|
||||||
self.display_surface = pygame.display.get_surface()
|
|
||||||
|
|
||||||
# Sprite Group setup
|
|
||||||
self.visible_sprites = Camera()
|
|
||||||
self.obstacle_sprites = pygame.sprite.Group()
|
|
||||||
self.attack_sprites = pygame.sprite.Group()
|
|
||||||
self.attackable_sprites = pygame.sprite.Group()
|
|
||||||
|
|
||||||
# Sprite setup and entity generation
|
|
||||||
self.create_map(n_players)
|
|
||||||
self.get_players_enemies()
|
|
||||||
self.get_distance_direction()
|
|
||||||
if not reset:
|
|
||||||
for player in self.player_sprites:
|
|
||||||
player.get_max_num_states()
|
|
||||||
player.setup_agent()
|
|
||||||
else:
|
|
||||||
for player in self.player_sprites:
|
|
||||||
player.get_max_num_states()
|
|
||||||
self.dead_players = np.zeros(len(self.player_sprites))
|
|
||||||
|
|
||||||
# UI setup
|
|
||||||
self.ui = UI()
|
|
||||||
|
|
||||||
def create_map(self, n_players):
|
|
||||||
player_id = 0
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
asset_path = os.path.join(
|
|
||||||
script_dir, '..', 'assets')
|
|
||||||
layouts = {
|
|
||||||
'boundary': import_csv_layout(f"{asset_path}/map/FloorBlocks.csv"),
|
|
||||||
'grass': import_csv_layout(f"{asset_path}/map/Grass.csv"),
|
|
||||||
'objects': import_csv_layout(f"{asset_path}/map/Objects.csv"),
|
|
||||||
'entities': import_csv_layout(f"{asset_path}/map/Entities.csv")
|
|
||||||
}
|
|
||||||
|
|
||||||
graphics = {
|
|
||||||
'grass': import_folder(f"{asset_path}/graphics/grass"),
|
|
||||||
'objects': import_folder(f"{asset_path}/graphics/objects")
|
|
||||||
}
|
|
||||||
|
|
||||||
for style, layout in layouts.items():
|
|
||||||
for row_index, row in enumerate(layout):
|
|
||||||
for col_index, col in enumerate(row):
|
|
||||||
if col != '-1':
|
|
||||||
x = col_index * TILESIZE
|
|
||||||
y = row_index * TILESIZE
|
|
||||||
if style == 'boundary':
|
|
||||||
Tile((x, y), [self.obstacle_sprites], 'invisible')
|
|
||||||
|
|
||||||
if style == 'grass':
|
|
||||||
random_grass_image = choice(graphics['grass'])
|
|
||||||
Tile((x, y), [self.visible_sprites, self.obstacle_sprites,
|
|
||||||
self.attackable_sprites], 'grass', random_grass_image)
|
|
||||||
#
|
|
||||||
# if style == 'objects':
|
|
||||||
# surf = graphics['objects'][int(col)]
|
|
||||||
# Tile((x, y), [self.visible_sprites,
|
|
||||||
# self.obstacle_sprites], 'object', surf)
|
|
||||||
|
|
||||||
if style == 'entities':
|
|
||||||
# The numbers represent their IDs in .csv files generated from TILED.
|
|
||||||
if col == '500':
|
|
||||||
self.observer = Observer(
|
|
||||||
(x, y), [self.visible_sprites])
|
|
||||||
|
|
||||||
elif col == '400':
|
|
||||||
if choice([0, 1]) == 1 and player_id <= n_players:
|
|
||||||
# Player Generation
|
|
||||||
Player(
|
|
||||||
(x, y),
|
|
||||||
[self.visible_sprites],
|
|
||||||
self.obstacle_sprites,
|
|
||||||
self.visible_sprites,
|
|
||||||
self.attack_sprites,
|
|
||||||
self.attackable_sprites,
|
|
||||||
'tank',
|
|
||||||
player_id)
|
|
||||||
|
|
||||||
player_id += 1
|
|
||||||
|
|
||||||
elif col == '401':
|
|
||||||
# Player Generation
|
|
||||||
Player(
|
|
||||||
(x, y),
|
|
||||||
[self.visible_sprites],
|
|
||||||
self.obstacle_sprites,
|
|
||||||
self.visible_sprites,
|
|
||||||
self.attack_sprites,
|
|
||||||
self.attackable_sprites,
|
|
||||||
'warrior',
|
|
||||||
player_id)
|
|
||||||
|
|
||||||
player_id += 1
|
|
||||||
|
|
||||||
elif col == '402':
|
|
||||||
# Player Generation
|
|
||||||
Player(
|
|
||||||
(x, y),
|
|
||||||
[self.visible_sprites],
|
|
||||||
self.obstacle_sprites,
|
|
||||||
self.visible_sprites,
|
|
||||||
self.attack_sprites,
|
|
||||||
self.attackable_sprites,
|
|
||||||
'mage',
|
|
||||||
player_id)
|
|
||||||
|
|
||||||
player_id += 1
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Monster Generation
|
|
||||||
if col == '390':
|
|
||||||
monster_name = 'bamboo'
|
|
||||||
elif col == '391':
|
|
||||||
monster_name = 'spirit'
|
|
||||||
elif col == '392':
|
|
||||||
monster_name = 'raccoon'
|
|
||||||
elif col == ' 393':
|
|
||||||
monster_name = 'squid'
|
|
||||||
|
|
||||||
Enemy(monster_name,
|
|
||||||
(x, y),
|
|
||||||
[
|
|
||||||
self.visible_sprites,
|
|
||||||
self.attackable_sprites
|
|
||||||
],
|
|
||||||
self.visible_sprites,
|
|
||||||
self.obstacle_sprites)
|
|
||||||
|
|
||||||
def get_players_enemies(self):
|
|
||||||
self.player_sprites = [sprite for sprite in self.visible_sprites.sprites(
|
|
||||||
) if hasattr(sprite, 'sprite_type') and sprite.sprite_type in ('player')]
|
|
||||||
|
|
||||||
self.enemy_sprites = [sprite for sprite in self.visible_sprites.sprites(
|
|
||||||
) if hasattr(sprite, 'sprite_type') and sprite.sprite_type in ('enemy')]
|
|
||||||
|
|
||||||
def get_distance_direction(self):
|
|
||||||
for player in self.player_sprites:
|
|
||||||
player.distance_direction_from_enemy = []
|
|
||||||
|
|
||||||
for enemy in self.enemy_sprites:
|
|
||||||
enemy.distance_direction_from_player = []
|
|
||||||
|
|
||||||
for player in self.player_sprites:
|
|
||||||
player_vector = pygame.math.Vector2(player.rect.center)
|
|
||||||
for enemy in self.enemy_sprites:
|
|
||||||
enemy_vector = pygame.math.Vector2(enemy.rect.center)
|
|
||||||
distance = (player_vector - enemy_vector).magnitude()
|
|
||||||
|
|
||||||
if distance > 0:
|
|
||||||
direction = (player_vector - enemy_vector).normalize()
|
|
||||||
else:
|
|
||||||
direction = pygame.math.Vector2()
|
|
||||||
|
|
||||||
enemy.distance_direction_from_player.append(
|
|
||||||
(distance, direction, player))
|
|
||||||
player.distance_direction_from_enemy.append(
|
|
||||||
(distance, -direction, enemy))
|
|
||||||
|
|
||||||
def apply_damage_to_player(self):
|
|
||||||
for enemy in self.enemy_sprites:
|
|
||||||
for distance, _, player in enemy.distance_direction_from_player:
|
|
||||||
if distance < enemy.stats.attack_radius and player._input.combat.vulnerable:
|
|
||||||
player.stats.health -= enemy.stats.attack
|
|
||||||
player._input.combat.vulnerable = False
|
|
||||||
player._input.combat.hurt_time = pygame.time.get_ticks()
|
|
||||||
|
|
||||||
def toggle_menu(self):
|
|
||||||
self.game_paused = not self.game_paused
|
|
||||||
|
|
||||||
def run(self, who='observer'):
|
|
||||||
# Draw the game
|
|
||||||
if who == 'observer':
|
|
||||||
self.visible_sprites.custom_draw(self.observer)
|
|
||||||
self.ui.display(self.observer)
|
|
||||||
else:
|
|
||||||
self.visible_sprites.custom_draw(self.player)
|
|
||||||
self.ui.display(self.aaa)
|
|
||||||
|
|
||||||
debug('v0.8')
|
|
||||||
|
|
||||||
if not self.game_paused:
|
|
||||||
# Update the game
|
|
||||||
for player in self.player_sprites:
|
|
||||||
player.attack_logic()
|
|
||||||
|
|
||||||
self.get_players_enemies()
|
|
||||||
self.get_distance_direction()
|
|
||||||
self.visible_sprites.update()
|
|
||||||
self.apply_damage_to_player()
|
|
||||||
|
|
||||||
else:
|
|
||||||
debug('PAUSED')
|
|
||||||
|
|
||||||
for player in self.player_sprites:
|
|
||||||
if player.is_dead():
|
|
||||||
self.dead_players[player.player_id] = True
|
|
||||||
|
|
||||||
self.done = True if self.dead_players.all() == 1 else False
|
|
|
@ -1,83 +0,0 @@
|
||||||
import random
|
|
||||||
import torch as T
|
|
||||||
import numpy as np
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from game import Game
|
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
from os import environ
|
|
||||||
environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
|
|
||||||
|
|
||||||
|
|
||||||
random.seed(1)
|
|
||||||
np.random.seed(1)
|
|
||||||
T.manual_seed(1)
|
|
||||||
|
|
||||||
n_episodes = 2000
|
|
||||||
game_len = 5000
|
|
||||||
|
|
||||||
figure_file = 'plots/scores_mp.png'
|
|
||||||
|
|
||||||
game = Game()
|
|
||||||
|
|
||||||
agent_list = [0 for _ in range(game.max_num_players)]
|
|
||||||
|
|
||||||
score_history = np.zeros(
|
|
||||||
shape=(game.max_num_players, n_episodes))
|
|
||||||
best_score = np.zeros(game.max_num_players)
|
|
||||||
avg_score = np.zeros(game.max_num_players)
|
|
||||||
|
|
||||||
for i in tqdm(range(n_episodes)):
|
|
||||||
# TODO: Make game.level.reset_map() so we don't __init__ everything all the time (such a waste)
|
|
||||||
if i != 0:
|
|
||||||
game.level.__init__(reset=True)
|
|
||||||
# TODO: Make game.level.reset_map() so we don't pull out and load the agent every time (There is -definitevly- a better way)
|
|
||||||
for player in game.level.player_sprites:
|
|
||||||
|
|
||||||
player.stats.exp = score_history[player.player_id][i-1]
|
|
||||||
for agent in agent_list:
|
|
||||||
player.agent = agent_list[player.player_id]
|
|
||||||
|
|
||||||
agent_list = [0 for _ in range(game.max_num_players)]
|
|
||||||
|
|
||||||
for j in range(game_len):
|
|
||||||
if not game.level.done:
|
|
||||||
|
|
||||||
game.run()
|
|
||||||
game.calc_score()
|
|
||||||
|
|
||||||
for player in game.level.player_sprites:
|
|
||||||
if player.is_dead():
|
|
||||||
agent_list[player.player_id] = player.agent
|
|
||||||
player.kill()
|
|
||||||
|
|
||||||
# if (j == game_len-1 or game.level.done) and game.level.enemy_sprites != []:
|
|
||||||
# for player in game.level.player_sprites:
|
|
||||||
# for enemy in game.level.enemy_sprites:
|
|
||||||
# player.stats.exp *= .95
|
|
||||||
|
|
||||||
for player in game.level.player_sprites:
|
|
||||||
if not player.is_dead():
|
|
||||||
agent_list[player.player_id] = player.agent
|
|
||||||
exp_points = player.stats.exp
|
|
||||||
score_history[player.player_id][i] = exp_points
|
|
||||||
avg_score[player.player_id] = np.mean(
|
|
||||||
score_history[player.player_id])
|
|
||||||
if avg_score[player.player_id] > best_score[player.player_id]:
|
|
||||||
best_score[player.player_id] = avg_score[player.player_id]
|
|
||||||
print(f"Saving models for agent {player.player_id}...")
|
|
||||||
player.agent.save_models(
|
|
||||||
actr_chkpt=f"player_{player.player_id}_actor", crtc_chkpt=f"player_{player.player_id}_critic")
|
|
||||||
print("Models saved ...\n")
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"\nCumulative score for player {player.player_id}: {score_history[0][i]}\nAverage score for player {player.player_id}: {avg_score[player.player_id]}\nBest score for player {player.player_id}: {best_score[player.player_id]}")
|
|
||||||
|
|
||||||
|
|
||||||
plt.plot(score_history)
|
|
||||||
plt.savefig(figure_file)
|
|
||||||
|
|
||||||
game.quit()
|
|
||||||
|
|
||||||
plt.show()
|
|
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
264
pneuma.py
Normal file
264
pneuma.py
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import torch as T
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from game import Game
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
# Create parser
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='Pneuma',
|
||||||
|
description='A Reinforcement Learning platform made with PyGame'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add args
|
||||||
|
parser.add_argument('--no_seed',
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Set to True to run without a seed.")
|
||||||
|
|
||||||
|
parser.add_argument('--seed',
|
||||||
|
type=int,
|
||||||
|
default=1,
|
||||||
|
help="The seed for the RNG.")
|
||||||
|
|
||||||
|
parser.add_argument('--n_episodes',
|
||||||
|
type=int,
|
||||||
|
default=300,
|
||||||
|
help="Number of episodes.")
|
||||||
|
|
||||||
|
parser.add_argument('--ep_length',
|
||||||
|
type=int,
|
||||||
|
default=5000,
|
||||||
|
help="Length of each episode.")
|
||||||
|
|
||||||
|
parser.add_argument('--n_players',
|
||||||
|
type=int,
|
||||||
|
default=1,
|
||||||
|
help="Number of players.")
|
||||||
|
|
||||||
|
parser.add_argument('--chkpt_path',
|
||||||
|
type=str,
|
||||||
|
default="agents/saved_models",
|
||||||
|
help="Save/load location for agent models.")
|
||||||
|
|
||||||
|
parser.add_argument('--figure_path',
|
||||||
|
type=str,
|
||||||
|
default="figures",
|
||||||
|
help="Save location for figures.")
|
||||||
|
|
||||||
|
parser.add_argument('--horizon',
|
||||||
|
type=int,
|
||||||
|
default=200,
|
||||||
|
help="The number of steps per update")
|
||||||
|
|
||||||
|
parser.add_argument('--show_pg',
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Set to True to open PyGame window on desktop")
|
||||||
|
|
||||||
|
parser.add_argument('--no_load',
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Set to True to ignore saved models")
|
||||||
|
|
||||||
|
parser.add_argument('--gamma',
|
||||||
|
type=float,
|
||||||
|
default=0.99,
|
||||||
|
help="The gamma parameter for PPO")
|
||||||
|
|
||||||
|
parser.add_argument('--alpha',
|
||||||
|
type=float,
|
||||||
|
default=0.0003,
|
||||||
|
help="The alpha parameter for PPO")
|
||||||
|
|
||||||
|
parser.add_argument('--policy_clip',
|
||||||
|
type=float,
|
||||||
|
default=0.2,
|
||||||
|
help="The policy clip")
|
||||||
|
|
||||||
|
parser.add_argument('--batch_size',
|
||||||
|
type=int,
|
||||||
|
default=64,
|
||||||
|
help="The size of each batch")
|
||||||
|
|
||||||
|
parser.add_argument('--n_epochs',
|
||||||
|
type=int,
|
||||||
|
default=10,
|
||||||
|
help="The number of epochs")
|
||||||
|
|
||||||
|
parser.add_argument('--gae_lambda',
|
||||||
|
type=float,
|
||||||
|
default=0.95,
|
||||||
|
help="The lambda parameter of the GAE")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
np.random.seed(args.seed)
|
||||||
|
T.manual_seed(args.seed)
|
||||||
|
|
||||||
|
n_episodes = args.n_episodes
|
||||||
|
episode_length = args.ep_length
|
||||||
|
n_players = args.n_players
|
||||||
|
|
||||||
|
chkpt_path = args.chkpt_path
|
||||||
|
figure_folder = args.figure_path
|
||||||
|
|
||||||
|
horizon = args.horizon
|
||||||
|
learnings_per_episode = int(episode_length/horizon)
|
||||||
|
learn_iters = 0
|
||||||
|
|
||||||
|
show_pygame = args.show_pg
|
||||||
|
|
||||||
|
# Setup AI stuff
|
||||||
|
score_history = np.zeros(shape=(n_players, n_episodes))
|
||||||
|
best_score = np.zeros(n_players)
|
||||||
|
|
||||||
|
actor_loss = np.zeros(shape=(n_players,
|
||||||
|
n_episodes))
|
||||||
|
|
||||||
|
critic_loss = np.zeros(shape=(n_players,
|
||||||
|
n_episodes))
|
||||||
|
|
||||||
|
total_loss = np.zeros(shape=(n_players,
|
||||||
|
n_episodes))
|
||||||
|
|
||||||
|
game = Game(show_pg=show_pygame, n_players=n_players)
|
||||||
|
|
||||||
|
print("Initializing agents ...")
|
||||||
|
for player in game.level.player_sprites:
|
||||||
|
player.setup_agent(
|
||||||
|
gamma=args.gamma,
|
||||||
|
alpha=args.alpha,
|
||||||
|
policy_clip=args.policy_clip,
|
||||||
|
batch_size=args.batch_size,
|
||||||
|
N=args.horizon,
|
||||||
|
n_epochs=args.n_epochs,
|
||||||
|
gae_lambda=args.gae_lambda,
|
||||||
|
chkpt_dir=chkpt_path,
|
||||||
|
no_load=args.no_load
|
||||||
|
)
|
||||||
|
|
||||||
|
# Episodes start
|
||||||
|
for episode in tqdm(range(n_episodes),
|
||||||
|
dynamic_ncols=True):
|
||||||
|
|
||||||
|
# This handles agent continuity, as well as score persistence
|
||||||
|
game.level.reset()
|
||||||
|
|
||||||
|
episode_actor_loss = np.zeros(
|
||||||
|
shape=(n_players, learnings_per_episode))
|
||||||
|
|
||||||
|
episode_critic_loss = np.zeros(
|
||||||
|
shape=(n_players, learnings_per_episode))
|
||||||
|
|
||||||
|
episode_total_loss = np.zeros(
|
||||||
|
shape=(n_players, learnings_per_episode))
|
||||||
|
|
||||||
|
# Main game loop
|
||||||
|
for step in tqdm(range(episode_length),
|
||||||
|
leave=False,
|
||||||
|
ascii=True,
|
||||||
|
dynamic_ncols=True):
|
||||||
|
|
||||||
|
if not game.level.done:
|
||||||
|
game.run()
|
||||||
|
if step % horizon == 0:
|
||||||
|
for player in game.level.player_sprites:
|
||||||
|
|
||||||
|
player.agent.learn()
|
||||||
|
|
||||||
|
episode_actor_loss[player.player_id][learn_iters % learnings_per_episode]\
|
||||||
|
= player.agent.actor_loss
|
||||||
|
|
||||||
|
episode_critic_loss[player.player_id][learn_iters % learnings_per_episode]\
|
||||||
|
= player.agent.critic_loss
|
||||||
|
|
||||||
|
episode_total_loss[player.player_id][learn_iters % learnings_per_episode]\
|
||||||
|
= player.agent.total_loss
|
||||||
|
|
||||||
|
learn_iters += 1
|
||||||
|
|
||||||
|
# Gather information about the episode
|
||||||
|
for player in game.level.player_sprites:
|
||||||
|
|
||||||
|
# Update score
|
||||||
|
score_history[player.player_id][episode] = player.stats.exp
|
||||||
|
|
||||||
|
# Update actor/critic loss
|
||||||
|
actor_loss[player.player_id][episode] = np.mean(
|
||||||
|
episode_actor_loss)
|
||||||
|
|
||||||
|
critic_loss[player.player_id][episode] = np.mean(
|
||||||
|
episode_critic_loss)
|
||||||
|
|
||||||
|
total_loss[player.player_id][episode] = np.mean(
|
||||||
|
episode_total_loss)
|
||||||
|
|
||||||
|
# Check for new best score
|
||||||
|
if player.stats.exp > best_score[player.player_id]:
|
||||||
|
print(f"\nNew best score:\t {player.stats.exp}\
|
||||||
|
\nOld best score: \t {best_score[player.player_id]}")
|
||||||
|
|
||||||
|
best_score[player.player_id] = player.stats.exp
|
||||||
|
|
||||||
|
print(f"Saving models for player {player.player_id}...")
|
||||||
|
|
||||||
|
# Save models
|
||||||
|
player.agent.save_models(
|
||||||
|
f"A{player.player_id}",
|
||||||
|
f"C{player.player_id}")
|
||||||
|
|
||||||
|
print(f"Models saved to {chkpt_path}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"\nScore this round for player {player.player_id}:\
|
||||||
|
{player.stats.exp}")
|
||||||
|
|
||||||
|
# End of training session
|
||||||
|
print("End of episodes.\
|
||||||
|
\n Exiting game...")
|
||||||
|
game.quit()
|
||||||
|
|
||||||
|
plt.figure()
|
||||||
|
plt.title("Player Performance")
|
||||||
|
plt.xlabel("Episode")
|
||||||
|
plt.ylabel("Score")
|
||||||
|
plt.legend([f"Player {num}" for num in range(n_players)])
|
||||||
|
for player_score in score_history:
|
||||||
|
plt.plot(player_score)
|
||||||
|
plt.savefig(f"{figure_folder}/score.png")
|
||||||
|
|
||||||
|
plt.figure()
|
||||||
|
plt.suptitle("Actor Loss")
|
||||||
|
plt.xlabel("Episode")
|
||||||
|
plt.ylabel("Loss")
|
||||||
|
plt.legend([f"Agent {num}" for num in range(n_players)])
|
||||||
|
for actor in actor_loss:
|
||||||
|
plt.plot(actor)
|
||||||
|
plt.savefig(f"{figure_folder}/actor_loss.png")
|
||||||
|
|
||||||
|
plt.figure()
|
||||||
|
plt.suptitle("Critic Loss")
|
||||||
|
plt.xlabel("Episode")
|
||||||
|
plt.ylabel("Loss")
|
||||||
|
plt.legend([f"Agent {num}" for num in range(n_players)])
|
||||||
|
for critic in critic_loss:
|
||||||
|
plt.plot(critic)
|
||||||
|
plt.savefig(f"{figure_folder}/critic_loss.png")
|
||||||
|
|
||||||
|
plt.figure()
|
||||||
|
plt.suptitle("Total Loss")
|
||||||
|
plt.xlabel("Episode")
|
||||||
|
plt.ylabel("Loss")
|
||||||
|
plt.legend([f"Agent {num}" for num in range(n_players)])
|
||||||
|
for total in total_loss:
|
||||||
|
plt.plot(total)
|
||||||
|
plt.savefig(f"{figure_folder}/total_loss.png")
|
||||||
|
plt.show()
|
|
@ -1,85 +0,0 @@
|
||||||
import torch as T
|
|
||||||
import numpy as np
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from game import Game
|
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
from os import environ
|
|
||||||
environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
|
|
||||||
|
|
||||||
|
|
||||||
np.random.seed(1)
|
|
||||||
T.manual_seed(1)
|
|
||||||
|
|
||||||
n_episodes = 300
|
|
||||||
game_len = 10000
|
|
||||||
n_players = 8
|
|
||||||
|
|
||||||
figure_file = 'plots/score_sp.png'
|
|
||||||
|
|
||||||
game = Game(n_players)
|
|
||||||
|
|
||||||
agent = game.level.player_sprites[0].agent
|
|
||||||
|
|
||||||
score_history = np.zeros(shape=(game.max_num_players, n_episodes))
|
|
||||||
best_score = 0
|
|
||||||
avg_score = np.zeros(n_episodes)
|
|
||||||
|
|
||||||
for i in tqdm(range(n_episodes)):
|
|
||||||
# TODO: Make game.level.reset_map() so we don't __init__ everything all the time (such a waste)
|
|
||||||
if i != 0:
|
|
||||||
game.level.__init__(n_players, reset=True)
|
|
||||||
# TODO: Make game.level.reset_map() so we don't pull out and load the agent every time (There is -definitevly- a better way)
|
|
||||||
|
|
||||||
for player in game.level.player_sprites:
|
|
||||||
player.stats.exp = score_history[player.player_id][i-1]
|
|
||||||
player.agent = agent
|
|
||||||
|
|
||||||
for j in tqdm(range(game_len)):
|
|
||||||
if not game.level.done:
|
|
||||||
|
|
||||||
game.run()
|
|
||||||
game.calc_score()
|
|
||||||
|
|
||||||
for player in game.level.player_sprites:
|
|
||||||
if player.is_dead():
|
|
||||||
player.kill()
|
|
||||||
|
|
||||||
# if (j == game_len-1 or game.level.done) and game.level.enemy_sprites != []:
|
|
||||||
# for player in game.level.player_sprites:
|
|
||||||
# for enemy in game.level.enemy_sprites:
|
|
||||||
# player.stats.exp *= .95
|
|
||||||
|
|
||||||
for player in game.level.player_sprites:
|
|
||||||
exp_points = player.stats.exp
|
|
||||||
score_history[player.player_id][i] = exp_points
|
|
||||||
avg_score[i] = np.mean(score_history)
|
|
||||||
|
|
||||||
if avg_score[i] >= best_score:
|
|
||||||
print(
|
|
||||||
f"\nNew Best score: {avg_score[i]}\
|
|
||||||
\nOld Best score: {best_score}"
|
|
||||||
)
|
|
||||||
best_score = avg_score[i]
|
|
||||||
print("Saving models for agent...")
|
|
||||||
agent.save_models(
|
|
||||||
actr_chkpt="player_actor", crtc_chkpt="player_critic")
|
|
||||||
print("Models saved ...\n")
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
f"Average score of round: {avg_score[i]}\
|
|
||||||
\nBest score: {np.mean(best_score)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
print("\nEpisodes done, saving models...")
|
|
||||||
agent.save_models(
|
|
||||||
actr_chkpt="player_actor", crtc_chkpt="player_critic")
|
|
||||||
print("Models saved ...\n")
|
|
||||||
|
|
||||||
plt.plot(avg_score)
|
|
||||||
plt.savefig(figure_file)
|
|
||||||
game.quit()
|
|
||||||
|
|
||||||
plt.show()
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -2,11 +2,12 @@ import pygame
|
||||||
|
|
||||||
pygame.init()
|
pygame.init()
|
||||||
|
|
||||||
font = pygame.font.Font(None,30)
|
font = pygame.font.Font(None, 30)
|
||||||
|
|
||||||
def debug(info, y =10, x = 10):
|
|
||||||
|
def debug(info, y=10, x=10):
|
||||||
display_surface = pygame.display.get_surface()
|
display_surface = pygame.display.get_surface()
|
||||||
debug_surf = font.render(str(info), True, 'White')
|
debug_surf = font.render(str(info), True, 'White')
|
||||||
debug_rect = debug_surf.get_rect(topleft = (x,y))
|
debug_rect = debug_surf.get_rect(topleft=(x, y))
|
||||||
pygame.draw.rect(display_surface, 'Black', debug_rect)
|
pygame.draw.rect(display_surface, 'Black', debug_rect)
|
||||||
display_surface.blit(debug_surf, debug_rect)
|
display_surface.blit(debug_surf, debug_rect)
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import pygame
|
import pygame
|
||||||
from csv import reader
|
from csv import reader
|
||||||
from os import walk
|
import os
|
||||||
|
|
||||||
|
|
||||||
def import_csv_layout(path):
|
def import_csv_layout(path):
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
path = os.path.join(script_dir,
|
||||||
|
'..',
|
||||||
|
'assets',
|
||||||
|
path)
|
||||||
terrain_map = []
|
terrain_map = []
|
||||||
with open(path) as level_map:
|
with open(path) as level_map:
|
||||||
layout = reader(level_map, delimiter=',')
|
layout = reader(level_map, delimiter=',')
|
||||||
|
@ -13,11 +18,25 @@ def import_csv_layout(path):
|
||||||
|
|
||||||
|
|
||||||
def import_folder(path):
|
def import_folder(path):
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
path = os.path.join(script_dir,
|
||||||
|
'..',
|
||||||
|
'assets',
|
||||||
|
path)
|
||||||
surface_list = []
|
surface_list = []
|
||||||
|
|
||||||
for _, __, img_files in walk(path):
|
for _, __, img_files in os.walk(path):
|
||||||
for image in img_files:
|
for image in img_files:
|
||||||
full_path = f"{path}/{image}"
|
full_path = os.path.join(path, image)
|
||||||
image_surf = pygame.image.load(full_path).convert_alpha()
|
image_surf = pygame.image.load(full_path).convert_alpha()
|
||||||
|
|
||||||
surface_list.append(image_surf)
|
surface_list.append(image_surf)
|
||||||
return surface_list
|
return surface_list
|
||||||
|
|
||||||
|
|
||||||
|
def import_assets(path):
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
return os.path.join(script_dir,
|
||||||
|
'..',
|
||||||
|
'assets', path)
|
||||||
|
|
Loading…
Reference in a new issue