1 /*
2  * Copyright (c) 2007-2013 Scott Lembcke and Howling Moon Software
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22 module demo.Player;
23 
24 import core.stdc.stdlib;
25 
26 import std.math;
27 
28 alias M_PI_2 = PI_2;
29 
30 import demo.dchip;
31 
32 import demo.ChipmunkDebugDraw;
33 import demo.ChipmunkDemo;
34 import demo.types;
35 
36 enum PLAYER_VELOCITY = 500.0;
37 
38 enum PLAYER_GROUND_ACCEL_TIME = 0.1;
39 enum PLAYER_GROUND_ACCEL = (PLAYER_VELOCITY / PLAYER_GROUND_ACCEL_TIME);
40 
41 enum PLAYER_AIR_ACCEL_TIME = 0.25;
42 enum PLAYER_AIR_ACCEL = (PLAYER_VELOCITY / PLAYER_AIR_ACCEL_TIME);
43 
44 enum JUMP_HEIGHT = 50.0;
45 enum JUMP_BOOST_HEIGHT = 55.0;
46 enum FALL_VELOCITY = 900.0;
47 enum GRAVITY = 2000.0;
48 
49 static cpBody * playerBody  = null;
50 static cpShape* playerShape = null;
51 
52 static cpFloat remainingBoost = 0;
53 static cpBool  grounded       = cpFalse;
54 static cpBool  lastJumpState  = cpFalse;
55 
56 static void SelectPlayerGroundNormal(cpBody* body_, cpArbiter* arb, cpVect* groundNormal)
57 {
58     cpVect n = cpvneg(cpArbiterGetNormal(arb, 0));
59 
60     if (n.y > groundNormal.y)
61     {
62         (*groundNormal) = n;
63     }
64 }
65 
66 static void playerUpdateVelocity(cpBody* body_, cpVect gravity, cpFloat damping, cpFloat dt)
67 {
68     int jumpState = (ChipmunkDemoKeyboard.y > 0.0f);
69 
70     // Grab the grounding normal from last frame
71     cpVect groundNormal = cpvzero;
72     cpBodyEachArbiter(playerBody, safeCast!cpBodyArbiterIteratorFunc(&SelectPlayerGroundNormal), &groundNormal);
73 
74     grounded = (groundNormal.y > 0.0);
75 
76     if (groundNormal.y < 0.0f)
77         remainingBoost = 0.0f;
78 
79     // Do a normal-ish update
80     cpBool boost = (jumpState && remainingBoost > 0.0f);
81     cpVect g     = (boost ? cpvzero : gravity);
82     cpBodyUpdateVelocity(body_, g, damping, dt);
83 
84     // Target horizontal speed for air/ground control
85     cpFloat target_vx = PLAYER_VELOCITY * ChipmunkDemoKeyboard.x;
86 
87     // Update the surface velocity and friction
88     cpVect surface_v = cpv(target_vx, 0.0);
89     playerShape.surface_v = surface_v;
90     playerShape.u         = (grounded ? PLAYER_GROUND_ACCEL / GRAVITY : 0.0);
91 
92     // Apply air control if not grounded
93     if (!grounded)
94     {
95         // Smoothly accelerate the velocity
96         playerBody.v.x = cpflerpconst(playerBody.v.x, target_vx, PLAYER_AIR_ACCEL * dt);
97     }
98 
99     body_.v.y = cpfclamp(body_.v.y, -FALL_VELOCITY, INFINITY);
100 }
101 
102 static void update(cpSpace* space, double dt)
103 {
104     int jumpState = (ChipmunkDemoKeyboard.y > 0.0f);
105 
106     // If the jump key was just pressed this frame, jump!
107     if (jumpState && !lastJumpState && grounded)
108     {
109         cpFloat jump_v = cpfsqrt(2.0 * JUMP_HEIGHT * GRAVITY);
110         playerBody.v = cpvadd(playerBody.v, cpv(0.0, jump_v));
111 
112         remainingBoost = JUMP_BOOST_HEIGHT / jump_v;
113     }
114 
115     // Step the space
116     cpSpaceStep(space, dt);
117 
118     remainingBoost -= dt;
119     lastJumpState   = cast(bool)jumpState;
120 }
121 
122 static cpSpace* init()
123 {
124     cpSpace* space = cpSpaceNew();
125     space.iterations = 10;
126     space.gravity    = cpv(0, -GRAVITY);
127 
128     //	space.sleepTimeThreshold = 1000;
129     space.enableContactGraph = cpTrue;
130 
131     cpBody * body_;
132     cpBody * staticBody = space.staticBody;
133     cpShape* shape;
134 
135     // Create segments around the edge of the screen.
136     shape         = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, -240), cpv(-320, 240), 0.0f));
137     shape.e      = 1.0f;
138     shape.u      = 1.0f;
139     shape.layers = NOT_GRABABLE_MASK;
140 
141     shape         = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(320, -240), cpv(320, 240), 0.0f));
142     shape.e      = 1.0f;
143     shape.u      = 1.0f;
144     shape.layers = NOT_GRABABLE_MASK;
145 
146     shape         = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, -240), cpv(320, -240), 0.0f));
147     shape.e      = 1.0f;
148     shape.u      = 1.0f;
149     shape.layers = NOT_GRABABLE_MASK;
150 
151     shape         = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, cpv(-320, 240), cpv(320, 240), 0.0f));
152     shape.e      = 1.0f;
153     shape.u      = 1.0f;
154     shape.layers = NOT_GRABABLE_MASK;
155 
156     // Set up the player
157     body_    = cpSpaceAddBody(space, cpBodyNew(1.0f, INFINITY));
158     body_.p = cpv(0, -200);
159     body_.velocity_func = &playerUpdateVelocity;
160     playerBody = body_;
161 
162     shape = cpSpaceAddShape(space, cpBoxShapeNew3(body_, cpBBNew(-15.0, -27.5, 15.0, 27.5), 10.0));
163 
164     //	shape = cpSpaceAddShape(space, cpSegmentShapeNew(playerBody, cpvzero, cpv(0, radius), radius));
165     shape.e = 0.0f;
166     shape.u = 0.0f;
167     shape.collision_type = 1;
168     playerShape = shape;
169 
170     // Add some boxes to jump on
171     for (int i = 0; i < 6; i++)
172     {
173         for (int j = 0; j < 3; j++)
174         {
175             body_    = cpSpaceAddBody(space, cpBodyNew(4.0f, INFINITY));
176             body_.p = cpv(100 + j * 60, -200 + i * 60);
177 
178             shape    = cpSpaceAddShape(space, cpBoxShapeNew(body_, 50, 50));
179             shape.e = 0.0f;
180             shape.u = 0.7f;
181         }
182     }
183 
184     return space;
185 }
186 
187 static void destroy(cpSpace* space)
188 {
189     ChipmunkDemoFreeSpaceChildren(space);
190     cpSpaceFree(space);
191 }
192 
193 ChipmunkDemo Player = {
194     "Platformer Player Controls",
195     1.0 / 180.0,
196     &init,
197     &update,
198     &ChipmunkDemoDefaultDrawImpl,
199     &destroy,
200 };