forked from treeform/simple-fps
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main-01-handmade.py
executable file
·203 lines (158 loc) · 6.24 KB
/
main-01-handmade.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#! /usr/bin/python2
import sys
from direct.showbase.ShowBase import ShowBase
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from panda3d.core import Vec3, KeyboardButton
from panda3d.core import CollisionTraverser, CollisionHandlerPusher, CollisionNode, CollisionSphere
from panda3d.core import CollisionRay, BitMask32, CollisionHandlerQueue
from panda3d.physics import ActorNode, ForceNode, LinearVectorForce
from panda3d.physics import PhysicsCollisionHandler
from direct.task import Task
# Before diving into this example, I *strongly* recommend
# to go through panda cheatsheets at
# https://www.panda3d.org/manual/index.php/Cheat_Sheets
# Right now it is all in one class. Perfectly sufficient for
# a small project like this.
class FPS(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# Load level.
# It must have
# <Group> Cube {
# <Collide> { Polyset keep descend }
# in the egg file
# IMPORTANT: The name of the group matters for collisions.
self.scene = self.loader.loadModel("level")
self.scene.reparentTo(self.render)
# Now we need player node.
# Right now it is used only for camera, more in the future
self.player = self.render.attachNewNode(PandaNode("player"))
self.player.setPos(0, 0, 2.5)
# Reparent camera to player so that it is rotated when player is
self.camera.reparentTo(self.player)
# Adjust camera lens
pl = self.cam.node().getLens()
pl.setFov(70)
pl.setNearFar(0.3, 300)
self.cam.node().setLens(pl)
# Exit when escape is pressed
self.accept("escape", sys.exit)
# Set up physics
self.setupHandmadePlayerPhysics()
def setupHandmadePlayerPhysics(self):
# Some default values (see frame update function
# for their meaning)
self.camH = 0
self.camP = 0
self.playerH = 0
self.height = 1.8
self.speed = 50
self.vspeed = 0
self.onGround = False
self.direction = Vec3(0, 0, 0)
# Setup collisions
self.cTrav = CollisionTraverser()
self.pusher = CollisionHandlerPusher()
cn = CollisionNode('player')
cn.addSolid(CollisionSphere(0,0,0,.5))
solid = self.player.attachNewNode(cn)
self.cTrav.addCollider(solid, base.pusher)
self.pusher.addCollider(solid, self.player, self.drive.node())
ray = CollisionRay()
ray.setOrigin(0,0,-.2)
ray.setDirection(0,0,-1)
cn = CollisionNode('playerRay')
cn.addSolid(ray)
cn.setFromCollideMask(BitMask32.bit(0))
cn.setIntoCollideMask(BitMask32.allOff())
solid = self.player.attachNewNode(cn)
self.nodeGroundHandler = CollisionHandlerQueue()
self.cTrav.addCollider(solid, self.nodeGroundHandler)
# Register frame update function
self.taskMgr.add(self.handmadePlayerPhysicsFrameUpdate, "FrameUpdateTask")
def handmadePlayerPhysicsFrameUpdate(self, task):
# Read mouse and keyboard
md = self.win.getPointer(0)
if self.win.movePointer(0, self.win.getXSize()/2, self.win.getYSize()/2):
dx = (md.getX() - self.win.getXSize()/2)*0.1
dy = (md.getY() - self.win.getYSize()/2)*0.1
else:
dx = 0
dy = 0
# Rotate camera ("head"). If on the ground, the rotation will
# be used for player node ("body") instead of "head"
self.camH = self.camH - dx
self.camP = self.camP - dy;
# Update only if on the ground, not when flying
if self.onGround:
# Read keyboard
isDown = base.mouseWatcherNode.is_button_down
# Update speed
if isDown(KeyboardButton.lshift()):
self.speed = 10
else:
self.speed = 5
# Compute new direction
self.direction = Vec3(0, 0, 0)
if isDown(KeyboardButton.ascii_key('w')):
self.direction = self.direction + Vec3.forward()
if isDown(KeyboardButton.ascii_key('s')):
self.direction = self.direction + Vec3.back()
if isDown(KeyboardButton.ascii_key('a')):
self.direction = self.direction + Vec3.left()
if isDown(KeyboardButton.ascii_key('d')):
self.direction = self.direction + Vec3.right()
# Jump by setting speed up
if isDown(KeyboardButton.ascii_key(' ')):
self.vspeed = 5
# Allign "body" to "head"
self.playerH = self.playerH + self.camH
self.camH = 0
self.camera.setPos(0, 0, 0) # because camera is player's child
# Update rotations
self.player.setH(self.playerH)
self.camera.setH(self.camH)
self.camera.setP(self.camP)
# Apply direction to player position
if self.direction.normalize():
self.player.setPos(self.player, self.direction*self.speed*globalClock.getDt())
# Finally, floor collisions. First find the highest plane below player.
highestZ = -100
for i in range(self.nodeGroundHandler.getNumEntries()):
entry = self.nodeGroundHandler.getEntry(i)
z = entry.getSurfacePoint(render).getZ()
if z > highestZ and entry.getIntoNode().getName() == "Cube":
highestZ = z
# First handle free-fall. If the highest plane is below player or we are "falling" up (after jumping)...
if (highestZ != -100 and highestZ < self.player.getZ()-self.height) or (self.vspeed > 0):
print("falling: "+str(self.player.getZ()-highestZ)) + " " + str(self.vspeed)
# ... compute new player height ...
newPlayerZ = self.player.getZ()+self.vspeed*globalClock.getDt()
# ... and if new height is still above floor ...
if newPlayerZ > highestZ+self.height:
# ... move player to floor level and increase vertical speed.
self.player.setZ(newPlayerZ)
self.vspeed = self.vspeed - 9.81*globalClock.getDt()
self.onGround = False
# ... but if new height is below floor ...
else:
# ... put player right on the floor, but keep vertical speed.
# Keeping vertical speed is needed to not jump when running down the hill
self.player.setZ(highestZ+self.height)
self.onGround = True
# Now handle player slightly below floor, just by putting it to floor level and resetting vspeed
# This can happen for example by arithmetic errors
elif highestZ > self.player.getZ()-self.height:
print("from under: "+str(self.player.getZ()-highestZ)) + " " + str(self.vspeed)
self.player.setZ(highestZ+self.height)
self.vspeed = 0
self.onGround = True
# Now the player is on the ground (first branch did not fire) but vspeed is not zero
# This can happen when vspeed is kept in the first branch
elif self.vspeed != 0:
print("vspeed < 0: "+str(self.player.getZ()-highestZ)) + " " + str(self.vspeed)
self.vspeed = 0
self.onGround = True
return task.cont
app = FPS()
app.run()