├── Fix64.cs ├── Fix64.cs.meta ├── LICENSE ├── README.md ├── UnityTools.cs ├── UnityTools.cs.meta ├── core.meta └── core ├── BattleLogic.cs ├── BattleLogic.cs.meta ├── GameData.cs ├── GameData.cs.meta ├── LockStepLogic.cs ├── LockStepLogic.cs.meta ├── SRandom.cs ├── SRandom.cs.meta ├── action.meta ├── action ├── ActionMainManager.cs ├── ActionMainManager.cs.meta ├── ActionManager.cs ├── ActionManager.cs.meta ├── BaseAction.cs ├── BaseAction.cs.meta ├── DelayDo.cs ├── DelayDo.cs.meta ├── MoveTo.cs ├── MoveTo.cs.meta ├── MyDelegates.cs └── MyDelegates.cs.meta ├── base.meta ├── base ├── BaseObject.cs ├── BaseObject.cs.meta ├── LiveObject.cs ├── LiveObject.cs.meta ├── UnityObject.cs └── UnityObject.cs.meta ├── bullet.meta ├── bullet ├── BaseBullet.cs ├── BaseBullet.cs.meta ├── BulletFactory.cs ├── BulletFactory.cs.meta ├── DirectionShootBullet.cs └── DirectionShootBullet.cs.meta ├── soldier.meta ├── soldier ├── BaseSoldier.cs ├── BaseSoldier.cs.meta ├── Grizzly.cs ├── Grizzly.cs.meta ├── SoldierFactory.cs └── SoldierFactory.cs.meta ├── state.meta ├── state ├── BaseState.cs ├── BaseState.cs.meta ├── CoolingState.cs ├── CoolingState.cs.meta ├── NormalState.cs ├── NormalState.cs.meta ├── StateMachine.cs ├── StateMachine.cs.meta ├── TowerAttackState.cs ├── TowerAttackState.cs.meta ├── TowerStandState.cs └── TowerStandState.cs.meta ├── tower.meta └── tower ├── BaseTower.cs ├── BaseTower.cs.meta ├── MagicStand.cs ├── MagicStand.cs.meta ├── TowerFactory.cs └── TowerFactory.cs.meta /Fix64.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 定点数 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 03/7/2018 6 | // 7 | // 8 | // 9 | 10 | using System; 11 | using System.IO; 12 | 13 | public partial struct Fix64 : IEquatable, IComparable { 14 | readonly long m_rawValue; 15 | 16 | public static readonly decimal Precision = (decimal)(new Fix64(1L)); 17 | public static readonly Fix64 One = new Fix64(ONE); 18 | public static readonly Fix64 Zero = new Fix64(); 19 | public static readonly Fix64 PI = new Fix64(Pi); 20 | public static readonly Fix64 PITimes2 = new Fix64(PiTimes2); 21 | public static readonly Fix64 PIOver180 = new Fix64((long)72); 22 | public static readonly Fix64 Rad2Deg = Fix64.Pi * (Fix64)2 / (Fix64)360; 23 | public static readonly Fix64 Deg2Rad = (Fix64)360 / (Fix64.Pi * (Fix64)2); 24 | 25 | const long Pi = 12868; 26 | const long PiTimes2 = 25736; 27 | 28 | public const int FRACTIONAL_PLACES = 12; 29 | const long ONE = 1L << FRACTIONAL_PLACES; 30 | 31 | public static int Sign(Fix64 value) { 32 | return 33 | value.m_rawValue < 0 ? -1 : 34 | value.m_rawValue > 0 ? 1 : 35 | 0; 36 | } 37 | 38 | public static Fix64 Abs(Fix64 value) { 39 | return new Fix64(value.m_rawValue > 0 ? value.m_rawValue : -value.m_rawValue); 40 | } 41 | 42 | public static Fix64 Floor(Fix64 value) { 43 | return new Fix64((long)((ulong)value.m_rawValue & 0xFFFFFFFFFFFFF000)); 44 | } 45 | 46 | public static Fix64 Ceiling(Fix64 value) { 47 | var hasFractionalPart = (value.m_rawValue & 0x0000000000000FFF) != 0; 48 | return hasFractionalPart ? Floor(value) + One : value; 49 | } 50 | 51 | public static Fix64 operator +(Fix64 x, Fix64 y) { 52 | return new Fix64(x.m_rawValue + y.m_rawValue); 53 | } 54 | 55 | public static Fix64 operator +(Fix64 x, int y) 56 | { 57 | return x + (Fix64)y; 58 | } 59 | 60 | public static Fix64 operator +(int x, Fix64 y) 61 | { 62 | return (Fix64)x + y; 63 | } 64 | 65 | public static Fix64 operator +(Fix64 x, float y) 66 | { 67 | return x + (Fix64)y; 68 | } 69 | 70 | public static Fix64 operator +(float x, Fix64 y) 71 | { 72 | return (Fix64)x + y; 73 | } 74 | 75 | public static Fix64 operator +(Fix64 x, double y) 76 | { 77 | return x + (Fix64)y; 78 | } 79 | 80 | public static Fix64 operator +(double x, Fix64 y) 81 | { 82 | return (Fix64)x + y; 83 | } 84 | 85 | public static Fix64 operator -(Fix64 x, Fix64 y) { 86 | return new Fix64(x.m_rawValue - y.m_rawValue); 87 | } 88 | 89 | public static Fix64 operator -(Fix64 x, int y) 90 | { 91 | return x - (Fix64)y; 92 | } 93 | 94 | public static Fix64 operator -(int x, Fix64 y) 95 | { 96 | return (Fix64)x - y; 97 | } 98 | 99 | public static Fix64 operator -(Fix64 x, float y) 100 | { 101 | return x - (Fix64)y; 102 | } 103 | 104 | public static Fix64 operator -(float x, Fix64 y) 105 | { 106 | return (Fix64)x + y; 107 | } 108 | 109 | public static Fix64 operator -(Fix64 x, double y) 110 | { 111 | return x - (Fix64)y; 112 | } 113 | 114 | public static Fix64 operator -(double x, Fix64 y) 115 | { 116 | return (Fix64)x - y; 117 | } 118 | 119 | 120 | public static Fix64 operator *(Fix64 x, Fix64 y) { 121 | return new Fix64((x.m_rawValue * y.m_rawValue) >> FRACTIONAL_PLACES); 122 | } 123 | 124 | public static Fix64 operator *(Fix64 x, int y) 125 | { 126 | return x * (Fix64)y; 127 | } 128 | 129 | public static Fix64 operator *(int x, Fix64 y) 130 | { 131 | return (Fix64)x * y; 132 | } 133 | 134 | public static Fix64 operator *(Fix64 x, float y) 135 | { 136 | return x * (Fix64)y; 137 | } 138 | 139 | public static Fix64 operator *(float x, Fix64 y) 140 | { 141 | return (Fix64)x * y; 142 | } 143 | 144 | public static Fix64 operator *(Fix64 x, double y) 145 | { 146 | return x * (Fix64)y; 147 | } 148 | 149 | public static Fix64 operator *(double x, Fix64 y) 150 | { 151 | return (Fix64)x * y; 152 | } 153 | 154 | public static Fix64 operator /(Fix64 x, Fix64 y) { 155 | return new Fix64((x.m_rawValue << FRACTIONAL_PLACES) / y.m_rawValue); 156 | } 157 | 158 | public static Fix64 operator /(Fix64 x, int y) 159 | { 160 | return x / (Fix64)y; 161 | } 162 | 163 | public static Fix64 operator /(int x, Fix64 y) 164 | { 165 | return (Fix64)x / y; 166 | } 167 | 168 | public static Fix64 operator /(Fix64 x, float y) 169 | { 170 | return x / (Fix64)y; 171 | } 172 | 173 | public static Fix64 operator /(float x, Fix64 y) 174 | { 175 | return (Fix64)x / y; 176 | } 177 | 178 | public static Fix64 operator /(double x, Fix64 y) 179 | { 180 | return (Fix64)x / y; 181 | } 182 | 183 | public static Fix64 operator /(Fix64 x, double y) 184 | { 185 | return x / (Fix64)y; 186 | } 187 | 188 | public static Fix64 operator %(Fix64 x, Fix64 y) { 189 | return new Fix64(x.m_rawValue % y.m_rawValue); 190 | } 191 | 192 | public static Fix64 operator -(Fix64 x) { 193 | return new Fix64(-x.m_rawValue); 194 | } 195 | 196 | public static bool operator ==(Fix64 x, Fix64 y) { 197 | return x.m_rawValue == y.m_rawValue; 198 | } 199 | 200 | public static bool operator !=(Fix64 x, Fix64 y) { 201 | return x.m_rawValue != y.m_rawValue; 202 | } 203 | 204 | public static bool operator >(Fix64 x, Fix64 y) { 205 | return x.m_rawValue > y.m_rawValue; 206 | } 207 | 208 | public static bool operator >(Fix64 x, int y) 209 | { 210 | return x > (Fix64)y; 211 | } 212 | public static bool operator <(Fix64 x, int y) 213 | { 214 | return x < (Fix64)y; 215 | } 216 | public static bool operator >(Fix64 x, float y) 217 | { 218 | return x > (Fix64)y; 219 | } 220 | public static bool operator <(Fix64 x, float y) 221 | { 222 | return x < (Fix64)y; 223 | } 224 | 225 | public static bool operator <(Fix64 x, Fix64 y) { 226 | return x.m_rawValue < y.m_rawValue; 227 | } 228 | 229 | public static bool operator >=(Fix64 x, Fix64 y) { 230 | return x.m_rawValue >= y.m_rawValue; 231 | } 232 | 233 | public static bool operator <=(Fix64 x, Fix64 y) { 234 | return x.m_rawValue <= y.m_rawValue; 235 | } 236 | 237 | public static Fix64 operator >> (Fix64 x, int amount) 238 | { 239 | return new Fix64(x.RawValue >> amount); 240 | } 241 | 242 | public static Fix64 operator << (Fix64 x, int amount) 243 | { 244 | return new Fix64(x.RawValue << amount); 245 | } 246 | 247 | 248 | public static explicit operator Fix64(long value) { 249 | return new Fix64(value * ONE); 250 | } 251 | public static explicit operator long(Fix64 value) { 252 | return value.m_rawValue >> FRACTIONAL_PLACES; 253 | } 254 | public static explicit operator Fix64(float value) { 255 | return new Fix64((long)(value * ONE)); 256 | } 257 | public static explicit operator float(Fix64 value) { 258 | return (float)value.m_rawValue / ONE; 259 | } 260 | public static explicit operator int(Fix64 value) 261 | { 262 | return (int)((float)value); 263 | } 264 | public static explicit operator Fix64(double value) { 265 | return new Fix64((long)(value * ONE)); 266 | } 267 | public static explicit operator double(Fix64 value) { 268 | return (double)value.m_rawValue / ONE; 269 | } 270 | public static explicit operator Fix64(decimal value) { 271 | return new Fix64((long)(value * ONE)); 272 | } 273 | public static explicit operator decimal(Fix64 value) { 274 | return (decimal)value.m_rawValue / ONE; 275 | } 276 | 277 | public override bool Equals(object obj) 278 | { 279 | return obj is Fix64 && ((Fix64)obj).m_rawValue == m_rawValue; 280 | } 281 | 282 | public override int GetHashCode() { 283 | return m_rawValue.GetHashCode(); 284 | } 285 | 286 | public bool Equals(Fix64 other) { 287 | return m_rawValue == other.m_rawValue; 288 | } 289 | 290 | public int CompareTo(Fix64 other) { 291 | return m_rawValue.CompareTo(other.m_rawValue); 292 | } 293 | 294 | public override string ToString() { 295 | return ((decimal)this).ToString(); 296 | } 297 | public string ToStringRound(int round = 2) 298 | { 299 | return (float)Math.Round((float)this, round) + ""; 300 | } 301 | 302 | public static Fix64 FromRaw(long rawValue) { 303 | return new Fix64(rawValue); 304 | } 305 | 306 | public static Fix64 Pow(Fix64 x, int y) 307 | { 308 | if (y == 1) return x; 309 | Fix64 result = Fix64.Zero; 310 | Fix64 tmp = Pow(x, y / 2); 311 | if ((y & 1) != 0) //奇数 312 | { 313 | result = x * tmp * tmp; 314 | } 315 | else 316 | { 317 | result = tmp * tmp; 318 | } 319 | 320 | return result; 321 | } 322 | 323 | 324 | public long RawValue { get { return m_rawValue; } } 325 | 326 | 327 | Fix64(long rawValue) { 328 | m_rawValue = rawValue; 329 | } 330 | 331 | public Fix64(int value) { 332 | m_rawValue = value * ONE; 333 | } 334 | 335 | public static Fix64 Sqrt(Fix64 f, int numberIterations) 336 | { 337 | if (f.RawValue < 0) 338 | { 339 | throw new ArithmeticException("sqrt error"); 340 | } 341 | 342 | if (f.RawValue == 0) 343 | return Fix64.Zero; 344 | 345 | Fix64 k = f + Fix64.One >> 1; 346 | for (int i = 0; i < numberIterations; i++) 347 | k = (k + (f / k)) >> 1; 348 | 349 | if (k.RawValue < 0) 350 | throw new ArithmeticException("Overflow"); 351 | else 352 | return k; 353 | } 354 | 355 | public static Fix64 Sqrt(Fix64 f) 356 | { 357 | byte numberOfIterations = 8; 358 | if (f.RawValue > 0x64000) 359 | numberOfIterations = 12; 360 | if (f.RawValue > 0x3e8000) 361 | numberOfIterations = 16; 362 | return Sqrt(f, numberOfIterations); 363 | } 364 | 365 | 366 | #region Sin 367 | public static Fix64 Sin(Fix64 i) 368 | { 369 | Fix64 j = (Fix64)0; 370 | for (; i < Fix64.Zero; i += Fix64.FromRaw(PiTimes2)); 371 | if (i > Fix64.FromRaw(PiTimes2)) 372 | i %= Fix64.FromRaw(PiTimes2); 373 | 374 | Fix64 k = (i * Fix64.FromRaw(100000000)) / Fix64.FromRaw(7145244444); 375 | if (i != Fix64.Zero && i != Fix64.FromRaw(6434) && i != Fix64.FromRaw(Pi) && 376 | i != Fix64.FromRaw(19302) && i != Fix64.FromRaw(PiTimes2)) 377 | j = (i * Fix64.FromRaw(100000000)) / Fix64.FromRaw(7145244444) - k * Fix64.FromRaw(10); 378 | if (k <= Fix64.FromRaw(90)) 379 | return sin_lookup(k, j); 380 | if (k <= Fix64.FromRaw(180)) 381 | return sin_lookup(Fix64.FromRaw(180) - k, j); 382 | if (k <= Fix64.FromRaw(270)) 383 | return -sin_lookup(k - Fix64.FromRaw(180), j); 384 | else 385 | return -sin_lookup(Fix64.FromRaw(360) - k, j); 386 | } 387 | 388 | private static Fix64 sin_lookup(Fix64 i, Fix64 j) 389 | { 390 | if (j > 0 && j < Fix64.FromRaw(10) && i < Fix64.FromRaw(90)) 391 | return Fix64.FromRaw(SIN_TABLE[i.RawValue]) + 392 | ((Fix64.FromRaw(SIN_TABLE[i.RawValue + 1]) - Fix64.FromRaw(SIN_TABLE[i.RawValue])) / 393 | Fix64.FromRaw(10)) * j; 394 | else 395 | return Fix64.FromRaw(SIN_TABLE[i.RawValue]); 396 | } 397 | 398 | private static int[] SIN_TABLE = { 399 | 0, 71, 142, 214, 285, 357, 428, 499, 570, 641, 400 | 711, 781, 851, 921, 990, 1060, 1128, 1197, 1265, 1333, 401 | 1400, 1468, 1534, 1600, 1665, 1730, 1795, 1859, 1922, 1985, 402 | 2048, 2109, 2170, 2230, 2290, 2349, 2407, 2464, 2521, 2577, 403 | 2632, 2686, 2740, 2793, 2845, 2896, 2946, 2995, 3043, 3091, 404 | 3137, 3183, 3227, 3271, 3313, 3355, 3395, 3434, 3473, 3510, 405 | 3547, 3582, 3616, 3649, 3681, 3712, 3741, 3770, 3797, 3823, 406 | 3849, 3872, 3895, 3917, 3937, 3956, 3974, 3991, 4006, 4020, 407 | 4033, 4045, 4056, 4065, 4073, 4080, 4086, 4090, 4093, 4095, 408 | 4096 409 | }; 410 | #endregion 411 | 412 | 413 | #region Cos, Tan, Asin 414 | public static Fix64 Cos(Fix64 i) 415 | { 416 | return Sin(i + Fix64.FromRaw(6435)); 417 | } 418 | 419 | public static Fix64 Tan(Fix64 i) 420 | { 421 | return Sin(i) / Cos(i); 422 | } 423 | 424 | public static Fix64 Asin(Fix64 F) 425 | { 426 | bool isNegative = F < 0; 427 | F = Abs(F); 428 | 429 | if (F > Fix64.One) 430 | throw new ArithmeticException("Bad Asin Input:" + (double)F); 431 | 432 | Fix64 f1 = ((((Fix64.FromRaw(145103 >> FRACTIONAL_PLACES) * F) - 433 | Fix64.FromRaw(599880 >> FRACTIONAL_PLACES)* F) + 434 | Fix64.FromRaw(1420468 >> FRACTIONAL_PLACES)* F) - 435 | Fix64.FromRaw(3592413 >> FRACTIONAL_PLACES)* F) + 436 | Fix64.FromRaw(26353447 >> FRACTIONAL_PLACES); 437 | Fix64 f2 = PI / (Fix64)2 - (Sqrt(Fix64.One - F) * f1); 438 | 439 | return isNegative ? -f2 : f2; 440 | } 441 | #endregion 442 | 443 | #region ATan, ATan2 444 | public static Fix64 Atan(Fix64 F) 445 | { 446 | return Asin(F / Sqrt(Fix64.One + (F * F))); 447 | } 448 | 449 | public static Fix64 Atan2(Fix64 F1, Fix64 F2) 450 | { 451 | if (F2.RawValue == 0 && F1.RawValue == 0) 452 | return (Fix64)0; 453 | 454 | Fix64 result = (Fix64)0; 455 | if (F2 > 0) 456 | result = Atan(F1 / F2); 457 | else if (F2 < 0) 458 | { 459 | if (F1 >= (Fix64)0) 460 | result = (PI - Atan(Abs(F1 / F2))); 461 | else 462 | result = -(PI - Atan(Abs(F1 / F2))); 463 | } 464 | else 465 | result = (F1 >= (Fix64)0 ? PI : -PI) / (Fix64)2; 466 | 467 | return result; 468 | } 469 | #endregion 470 | } 471 | 472 | public struct FixVector3 473 | { 474 | public Fix64 x; 475 | public Fix64 y; 476 | public Fix64 z; 477 | 478 | public FixVector3(Fix64 x, Fix64 y, Fix64 z) 479 | { 480 | this.x = x; 481 | this.y = y; 482 | this.z = z; 483 | } 484 | 485 | public FixVector3(FixVector3 v) 486 | { 487 | this.x = v.x; 488 | this.y = v.y; 489 | this.z = v.z; 490 | } 491 | 492 | public Fix64 this[int index] 493 | { 494 | get 495 | { 496 | if (index == 0) 497 | return x; 498 | else if (index == 1) 499 | return y; 500 | else 501 | return z; 502 | } 503 | set 504 | { 505 | if (index == 0) 506 | x = value; 507 | else if (index == 1) 508 | y = value; 509 | else 510 | y = value; 511 | } 512 | } 513 | 514 | public static FixVector3 Zero 515 | { 516 | get { return new FixVector3(Fix64.Zero, Fix64.Zero, Fix64.Zero); } 517 | } 518 | 519 | public static FixVector3 operator +(FixVector3 a, FixVector3 b) 520 | { 521 | Fix64 x = a.x + b.x; 522 | Fix64 y = a.y + b.y; 523 | Fix64 z = a.z + b.z; 524 | return new FixVector3(x, y, z); 525 | } 526 | 527 | public static FixVector3 operator -(FixVector3 a, FixVector3 b) 528 | { 529 | Fix64 x = a.x - b.x; 530 | Fix64 y = a.y - b.y; 531 | Fix64 z = a.z - b.z; 532 | return new FixVector3(x, y, z); 533 | } 534 | 535 | public static FixVector3 operator *(Fix64 d, FixVector3 a) 536 | { 537 | Fix64 x = a.x * d; 538 | Fix64 y = a.y * d; 539 | Fix64 z = a.z * d; 540 | return new FixVector3(x, y, z); 541 | } 542 | 543 | public static FixVector3 operator *(FixVector3 a, Fix64 d) 544 | { 545 | Fix64 x = a.x * d; 546 | Fix64 y = a.y * d; 547 | Fix64 z = a.z * d; 548 | return new FixVector3(x, y, z); 549 | } 550 | 551 | public static FixVector3 operator /(FixVector3 a, Fix64 d) 552 | { 553 | Fix64 x = a.x / d; 554 | Fix64 y = a.y / d; 555 | Fix64 z = a.z / d; 556 | return new FixVector3(x, y, z); 557 | } 558 | 559 | public static bool operator ==(FixVector3 lhs, FixVector3 rhs) 560 | { 561 | return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z; 562 | } 563 | 564 | public static bool operator !=(FixVector3 lhs, FixVector3 rhs) 565 | { 566 | return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z; 567 | } 568 | 569 | public static Fix64 SqrMagnitude(FixVector3 a) 570 | { 571 | return a.x * a.x + a.y * a.y + a.z * a.z; 572 | } 573 | 574 | public static Fix64 Distance(FixVector3 a, FixVector3 b) 575 | { 576 | return Magnitude(a - b); 577 | } 578 | 579 | public static Fix64 Magnitude(FixVector3 a) 580 | { 581 | return Fix64.Sqrt(FixVector3.SqrMagnitude(a)); 582 | } 583 | 584 | public void Normalize() 585 | { 586 | Fix64 n = x * x + y * y + z * z; 587 | if (n == Fix64.Zero) 588 | return; 589 | 590 | n = Fix64.Sqrt(n); 591 | 592 | if (n < (Fix64)0.0001) 593 | { 594 | return; 595 | } 596 | 597 | n = 1 / n; 598 | x *= n; 599 | y *= n; 600 | z *= n; 601 | } 602 | 603 | public FixVector3 GetNormalized() 604 | { 605 | FixVector3 v = new FixVector3(this); 606 | v.Normalize(); 607 | return v; 608 | } 609 | 610 | public override string ToString() 611 | { 612 | return string.Format("x:{0} y:{1} z:{2}", x, y, z); 613 | } 614 | 615 | public override bool Equals(object obj) 616 | { 617 | return obj is FixVector2 && ((FixVector3)obj) == this; 618 | } 619 | 620 | public override int GetHashCode() 621 | { 622 | return this.x.GetHashCode() + this.y.GetHashCode() + this.z.GetHashCode(); 623 | } 624 | 625 | public static FixVector3 Lerp(FixVector3 from, FixVector3 to, Fix64 factor) 626 | { 627 | return from * (1 - factor) + to * factor; 628 | } 629 | #if _CLIENTLOGIC_ 630 | public UnityEngine.Vector3 ToVector3() 631 | { 632 | return new UnityEngine.Vector3((float)x, (float)y, (float)z); 633 | } 634 | #endif 635 | } 636 | 637 | public struct FixVector2 638 | { 639 | public Fix64 x; 640 | public Fix64 y; 641 | 642 | public FixVector2(Fix64 x, Fix64 y) 643 | { 644 | this.x = x; 645 | this.y = y; 646 | } 647 | public FixVector2(Fix64 x, int y) 648 | { 649 | this.x = x; 650 | this.y = (Fix64)y; 651 | } 652 | 653 | public FixVector2(int x, int y) 654 | { 655 | this.x = (Fix64)x; 656 | this.y = (Fix64)y; 657 | } 658 | public FixVector2(FixVector2 v) 659 | { 660 | this.x = v.x; 661 | this.y = v.y; 662 | } 663 | public static FixVector2 operator -(FixVector2 a, int b) 664 | { 665 | Fix64 x = a.x - b; 666 | Fix64 y = a.y - b; 667 | return new FixVector2(x, y); 668 | } 669 | 670 | public Fix64 this[int index] 671 | { 672 | get { return index == 0 ? x : y; } 673 | set 674 | { 675 | if (index == 0) 676 | { 677 | x = value; 678 | } 679 | else 680 | { 681 | y = value; 682 | } 683 | } 684 | } 685 | 686 | public static FixVector2 Zero 687 | { 688 | get { return new FixVector2(Fix64.Zero, Fix64.Zero); } 689 | } 690 | 691 | public static FixVector2 operator +(FixVector2 a, FixVector2 b) 692 | { 693 | Fix64 x = a.x + b.x; 694 | Fix64 y = a.y + b.y; 695 | return new FixVector2(x, y); 696 | } 697 | 698 | public static FixVector2 operator -(FixVector2 a, FixVector2 b) 699 | { 700 | Fix64 x = a.x - b.x; 701 | Fix64 y = a.y - b.y; 702 | return new FixVector2(x, y); 703 | } 704 | 705 | public static FixVector2 operator *(Fix64 d, FixVector2 a) 706 | { 707 | Fix64 x = a.x * d; 708 | Fix64 y = a.y * d; 709 | return new FixVector2(x, y); 710 | } 711 | 712 | public static FixVector2 operator *(FixVector2 a, Fix64 d) 713 | { 714 | Fix64 x = a.x * d; 715 | Fix64 y = a.y * d; 716 | return new FixVector2(x, y); 717 | } 718 | 719 | public static FixVector2 operator /(FixVector2 a, Fix64 d) 720 | { 721 | Fix64 x = a.x / d; 722 | Fix64 y = a.y / d; 723 | return new FixVector2(x, y); 724 | } 725 | 726 | public static bool operator ==(FixVector2 lhs, FixVector2 rhs) 727 | { 728 | return lhs.x == rhs.x && lhs.y == rhs.y; 729 | } 730 | 731 | public static bool operator !=(FixVector2 lhs, FixVector2 rhs) 732 | { 733 | return lhs.x != rhs.x || lhs.y != rhs.y; 734 | } 735 | 736 | public override bool Equals(object obj) 737 | { 738 | return obj is FixVector2 && ((FixVector2)obj) == this; 739 | } 740 | 741 | public override int GetHashCode() 742 | { 743 | return this.x.GetHashCode() + this.y.GetHashCode(); 744 | } 745 | 746 | 747 | public static Fix64 SqrMagnitude(FixVector2 a) 748 | { 749 | return a.x * a.x + a.y * a.y; 750 | } 751 | 752 | public static Fix64 Distance(FixVector2 a, FixVector2 b) 753 | { 754 | return Magnitude(a - b); 755 | } 756 | 757 | public static Fix64 Magnitude(FixVector2 a) 758 | { 759 | return Fix64.Sqrt(FixVector2.SqrMagnitude(a)); 760 | } 761 | 762 | public void Normalize() 763 | { 764 | Fix64 n = x * x + y * y; 765 | if (n == Fix64.Zero) 766 | return; 767 | 768 | n = Fix64.Sqrt(n); 769 | 770 | if (n < (Fix64)0.0001) 771 | { 772 | return; 773 | } 774 | 775 | n = 1 / n; 776 | x *= n; 777 | y *= n; 778 | } 779 | 780 | public FixVector2 GetNormalized() 781 | { 782 | FixVector2 v = new FixVector2(this); 783 | v.Normalize(); 784 | return v; 785 | } 786 | 787 | public override string ToString() 788 | { 789 | return string.Format("x:{0} y:{1}", x, y); 790 | } 791 | 792 | #if _CLIENTLOGIC_ 793 | public UnityEngine.Vector2 ToVector2() 794 | { 795 | return new UnityEngine.Vector2((float)x, (float)y); 796 | } 797 | #endif 798 | } 799 | 800 | public struct NormalVector2 801 | { 802 | public float x; 803 | public float y; 804 | 805 | public NormalVector2(float x, float y) 806 | { 807 | this.x = x; 808 | this.y = y; 809 | } 810 | 811 | 812 | public NormalVector2(int x, int y) 813 | { 814 | this.x = x; 815 | this.y = y; 816 | } 817 | public NormalVector2(NormalVector2 v) 818 | { 819 | this.x = v.x; 820 | this.y = v.y; 821 | } 822 | public static NormalVector2 operator -(NormalVector2 a, int b) 823 | { 824 | float x = a.x - b; 825 | float y = a.y - b; 826 | return new NormalVector2(x, y); 827 | } 828 | 829 | public float this[int index] 830 | { 831 | get { return index == 0 ? x : y; } 832 | set 833 | { 834 | if (index == 0) 835 | { 836 | x = value; 837 | } 838 | else 839 | { 840 | y = value; 841 | } 842 | } 843 | } 844 | 845 | public static NormalVector2 Zero 846 | { 847 | get { return new NormalVector2(0, 0); } 848 | } 849 | 850 | public static NormalVector2 operator +(NormalVector2 a, NormalVector2 b) 851 | { 852 | float x = a.x + b.x; 853 | float y = a.y + b.y; 854 | return new NormalVector2(x, y); 855 | } 856 | 857 | public static NormalVector2 operator -(NormalVector2 a, NormalVector2 b) 858 | { 859 | float x = a.x - b.x; 860 | float y = a.y - b.y; 861 | return new NormalVector2(x, y); 862 | } 863 | 864 | public static NormalVector2 operator *(float d, NormalVector2 a) 865 | { 866 | float x = a.x * d; 867 | float y = a.y * d; 868 | return new NormalVector2(x, y); 869 | } 870 | 871 | public static NormalVector2 operator *(NormalVector2 a, float d) 872 | { 873 | float x = a.x * d; 874 | float y = a.y * d; 875 | return new NormalVector2(x, y); 876 | } 877 | 878 | public static NormalVector2 operator /(NormalVector2 a, float d) 879 | { 880 | float x = a.x / d; 881 | float y = a.y / d; 882 | return new NormalVector2(x, y); 883 | } 884 | 885 | public static bool operator ==(NormalVector2 lhs, NormalVector2 rhs) 886 | { 887 | return lhs.x == rhs.x && lhs.y == rhs.y; 888 | } 889 | 890 | public static bool operator !=(NormalVector2 lhs, NormalVector2 rhs) 891 | { 892 | return lhs.x != rhs.x || lhs.y != rhs.y; 893 | } 894 | 895 | public override bool Equals(object obj) 896 | { 897 | return obj is NormalVector2 && ((NormalVector2)obj) == this; 898 | } 899 | 900 | public override int GetHashCode() 901 | { 902 | return this.x.GetHashCode() + this.y.GetHashCode(); 903 | } 904 | 905 | 906 | public static float SqrMagnitude(NormalVector2 a) 907 | { 908 | return a.x * a.x + a.y * a.y; 909 | } 910 | 911 | public static float Distance(NormalVector2 a, NormalVector2 b) 912 | { 913 | return Magnitude(a - b); 914 | } 915 | 916 | public static float Magnitude(NormalVector2 a) 917 | { 918 | return NormalVector2.SqrMagnitude(a); 919 | } 920 | 921 | public void Normalize() 922 | { 923 | float n = x * x + y * y; 924 | if (n == 0) 925 | return; 926 | 927 | //n = float.Sqrt(n); 928 | 929 | if (n < (float)0.0001) 930 | { 931 | return; 932 | } 933 | 934 | n = 1 / n; 935 | x *= n; 936 | y *= n; 937 | } 938 | 939 | public NormalVector2 GetNormalized() 940 | { 941 | NormalVector2 v = new NormalVector2(this); 942 | v.Normalize(); 943 | return v; 944 | } 945 | 946 | public override string ToString() 947 | { 948 | return string.Format("x:{0} y:{1}", x, y); 949 | } 950 | 951 | #if _CLIENTLOGIC_ 952 | public UnityEngine.Vector2 ToVector2() 953 | { 954 | return new UnityEngine.Vector2((float)x, (float)y); 955 | } 956 | #endif 957 | } 958 | 959 | public struct IntVector2 960 | { 961 | public int x; 962 | public int y; 963 | 964 | 965 | 966 | public IntVector2(int x, int y) 967 | { 968 | this.x = x; 969 | this.y = y; 970 | } 971 | public IntVector2(IntVector2 v) 972 | { 973 | this.x = v.x; 974 | this.y = v.y; 975 | } 976 | public static IntVector2 operator -(IntVector2 a, int b) 977 | { 978 | int x = a.x - b; 979 | int y = a.y - b; 980 | return new IntVector2(x, y); 981 | } 982 | 983 | public int this[int index] 984 | { 985 | get { return index == 0 ? x : y; } 986 | set 987 | { 988 | if (index == 0) 989 | { 990 | x = value; 991 | } 992 | else 993 | { 994 | y = value; 995 | } 996 | } 997 | } 998 | 999 | public static IntVector2 Zero 1000 | { 1001 | get { return new IntVector2(0, 0); } 1002 | } 1003 | 1004 | public static IntVector2 operator +(IntVector2 a, IntVector2 b) 1005 | { 1006 | int x = a.x + b.x; 1007 | int y = a.y + b.y; 1008 | return new IntVector2(x, y); 1009 | } 1010 | 1011 | public static IntVector2 operator -(IntVector2 a, IntVector2 b) 1012 | { 1013 | int x = a.x - b.x; 1014 | int y = a.y - b.y; 1015 | return new IntVector2(x, y); 1016 | } 1017 | 1018 | public static IntVector2 operator *(int d, IntVector2 a) 1019 | { 1020 | int x = a.x * d; 1021 | int y = a.y * d; 1022 | return new IntVector2(x, y); 1023 | } 1024 | 1025 | public static IntVector2 operator *(IntVector2 a, int d) 1026 | { 1027 | int x = a.x * d; 1028 | int y = a.y * d; 1029 | return new IntVector2(x, y); 1030 | } 1031 | 1032 | public static IntVector2 operator /(IntVector2 a, int d) 1033 | { 1034 | int x = a.x / d; 1035 | int y = a.y / d; 1036 | return new IntVector2(x, y); 1037 | } 1038 | 1039 | public static bool operator ==(IntVector2 lhs, IntVector2 rhs) 1040 | { 1041 | return lhs.x == rhs.x && lhs.y == rhs.y; 1042 | } 1043 | 1044 | public static bool operator !=(IntVector2 lhs, IntVector2 rhs) 1045 | { 1046 | return lhs.x != rhs.x || lhs.y != rhs.y; 1047 | } 1048 | 1049 | public override bool Equals(object obj) 1050 | { 1051 | return obj is IntVector2 && ((IntVector2)obj) == this; 1052 | } 1053 | 1054 | public override int GetHashCode() 1055 | { 1056 | return this.x.GetHashCode() + this.y.GetHashCode(); 1057 | } 1058 | 1059 | 1060 | public static int SqrMagnitude(IntVector2 a) 1061 | { 1062 | return a.x * a.x + a.y * a.y; 1063 | } 1064 | 1065 | public static int Distance(IntVector2 a, IntVector2 b) 1066 | { 1067 | return Magnitude(a - b); 1068 | } 1069 | 1070 | public static int Magnitude(IntVector2 a) 1071 | { 1072 | return IntVector2.SqrMagnitude(a); 1073 | } 1074 | 1075 | public void Normalize() 1076 | { 1077 | int n = x * x + y * y; 1078 | if (n == 0) 1079 | return; 1080 | 1081 | //n = int.Sqrt(n); 1082 | 1083 | if (n < (int)0.0001) 1084 | { 1085 | return; 1086 | } 1087 | 1088 | n = 1 / n; 1089 | x *= n; 1090 | y *= n; 1091 | } 1092 | 1093 | public IntVector2 GetNormalized() 1094 | { 1095 | IntVector2 v = new IntVector2(this); 1096 | v.Normalize(); 1097 | return v; 1098 | } 1099 | 1100 | public override string ToString() 1101 | { 1102 | return string.Format("x:{0} y:{1}", x, y); 1103 | } 1104 | 1105 | #if _CLIENTLOGIC_ 1106 | public UnityEngine.Vector2 ToVector2() 1107 | { 1108 | return new UnityEngine.Vector2((int)x, (int)y); 1109 | } 1110 | #endif 1111 | } -------------------------------------------------------------------------------- /Fix64.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ec4765977c843e14693d4f13db68a324 3 | timeCreated: 1534571798 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 林鹤 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LockStepSimpleFramework-Shared 2 | unity帧同步游戏极简框架-客户端服务器共享逻辑 3 | 4 | **阅前提示:** 5 | 此框架为有帧同步需求的游戏做一个简单的示例,实现了一个精简的框架,本文着重讲解帧同步游戏开发过程中需要注意的各种要点,伴随框架自带了一个小的塔防sample作为演示. 6 | 7 | 目录: 8 | 9 | [TOC] 10 | 11 | #哪些游戏需要使用帧同步 12 | 如果游戏中有如下需求,那这个游戏的开发框架应该使用帧同步: 13 |
  • 多人实时对战游戏 14 |
  • 游戏中需要战斗回放功能 15 |
  • 游戏中需要加速功能 16 |
  • 需要服务器同步逻辑校验防止作弊 17 | 18 | LockStep框架就是为了上面几种情况而设计的. 19 | 20 | #如何实现一个可行的帧同步框架 21 | 主要确保以下三点来保证帧同步的准确性: 22 |
  • 可靠稳定的帧同步基础算法 23 |
  • 消除浮点数带来的精度误差 24 |
  • 控制好随机数 25 | 26 | ##帧同步原理 27 | >相同的输入 + 相同的时机 = 相同的显示 28 | 29 | 客户端接受的输入是相同的,执行的逻辑帧也是一样的,那么每次得到的结果肯定也是同步一致的。为了让运行结果不与硬件运行速度快慢相关联,则不能用现实历经的时间(Time.deltaTime)作为差值阀值进行计算,而是使用固定的时间片段来作为阀值,这样无论两帧之间的真实时间间隔是多少,游戏逻辑执行的次数是恒定的,举例: 30 | 我们预设每个逻辑帧的时间跨度是1秒钟,那么当物理时间经过10秒后,逻辑便会运行10次,经过100秒便会运行100次,无论在运行速度快的机器上还是慢的机器上均是如此,不会因为两帧之间的跨度间隔而有所改变。 31 | 而渲染帧(一般为30到60帧),则是根据逻辑帧(10到20帧)去插值,从而得到一个“平滑”的展示,渲染帧只是逻辑帧的无限逼近插值,不过人眼一般无法分辨这种滞后性,因此可以把这两者理解为同步的. 32 | >如果硬件的运行速度赶不上逻辑帧的运行速度,则有可能出现逻辑执行多次后,渲染才执行一次的状况,如果遇到这种情况画面就会出现卡顿和丢帧的情况. 33 | 34 | ##帧同步算法 35 | ##基础核心算法 36 | 下面这段代码为帧同步的核心逻辑片段: 37 | ``` 38 | m_fAccumilatedTime = m_fAccumilatedTime + deltaTime; 39 | 40 | //如果真实累计的时间超过游戏帧逻辑原本应有的时间,则循环执行逻辑,确保整个逻辑的运算不会因为帧间隔时间的波动而计算出不同的结果 41 | while (m_fAccumilatedTime > m_fNextGameTime) { 42 | 43 | //运行与游戏相关的具体逻辑 44 | m_callUnit.frameLockLogic(); 45 | 46 | //计算下一个逻辑帧应有的时间 47 | m_fNextGameTime += m_fFrameLen; 48 | 49 | //游戏逻辑帧自增 50 | GameData.g_uGameLogicFrame += 1; 51 | } 52 | 53 | //计算两帧的时间差,用于运行补间动画 54 | m_fInterpolation = (m_fAccumilatedTime + m_fFrameLen - m_fNextGameTime) / m_fFrameLen; 55 | 56 | //更新渲染位置 57 | m_callUnit.updateRenderPosition(m_fInterpolation); 58 | ``` 59 | ##渲染更新机制 60 | 由于帧同步以及逻辑与渲染分离的设置,我们不能再去直接操作transform的localPosition,而设立一个虚拟的逻辑值进行代替,我们在游戏逻辑中,如果需要变更对象的位置,只需要更新这个虚拟的逻辑值,在一轮逻辑计算完毕后会根据这个值统一进行一轮渲染,这里我们引入了逻辑位置m_fixv3LogicPosition这个变量. 61 | ``` 62 | // 设置位置 63 | // 64 | // @param position 要设置到的位置 65 | // @return none 66 | public override void setPosition(FixVector3 position) 67 | { 68 | m_fixv3LogicPosition = position; 69 | } 70 | ``` 71 | 渲染流程如下: 72 | ![](https://img-blog.csdn.net/20180831094842873?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbnppMjE1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 73 | 74 | 只有需要移动的物体,我们才进行插值运算,不会移动的静止物体直接设置其坐标就可以了 75 | ``` 76 | //只有会移动的对象才需要采用插值算法补间动画,不会移动的对象直接设置位置即可 77 | if ((m_scType == "soldier" || m_scType == "bullet") && interpolation != 0) 78 | { 79 | m_gameObject.transform.localPosition = Vector3.Lerp(m_fixv3LastPosition.ToVector3(), m_fixv3LogicPosition.ToVector3(), interpolation); 80 | } 81 | else 82 | { 83 | m_gameObject.transform.localPosition = m_fixv3LogicPosition.ToVector3(); 84 | } 85 | ``` 86 | 87 | ##定点数 88 | >定点数和浮点数,是指在计算机中一个数的小数点的位置是固定的还是浮动的,如果一个数中小数点的位置是固定的,则为定点数;如果一个数中小数点的位置是浮动的,则为浮点数。定点数由于小数点的位置固定,因此其精度可控,相反浮点数的精度不可控. 89 | 90 | 对于帧同步框架来说,定点数是一个非常重要的特性,我们在在不同平台,甚至不同手机上运行一段完全相同的代码时有可能出现截然不同的结果,那是因为不同平台不同cpu对浮点数的处理结果有可能是不一致的,游戏中仅仅0.000000001的精度差距,都可能在多次计算后带来蝴蝶效应,导致完全不同的结果 91 | **举例**:当一个士兵进入塔的攻击范围时,塔会发动攻击,在手机A上的第100帧时,士兵进入了攻击范围,触发了攻击,而在手机B上因为一点点误差,导致101帧时才触发攻击,虽然只差了一帧,但后续会因为这一帧的偏差带来之后更多更大的偏差,从这一帧的不同开始,这已经是两场截然不同的战斗了. 92 | 因此我们必须使用定点数来消除精度误差带来的不可预知的结果,让同样的战斗逻辑在任何硬件,任何操作系统下运行都能得到同样的结果.同时也再次印证文章最开始提到的帧同步核心原理: 93 | **相同的输入 + 相同的时机 = 相同的显示** 94 | 框架自带了一套完整的定点数库Fix64.cs,其中对浮点数与定点数相互转换,操作符重载都做好了封装,我们可以像使用普通浮点数那样来使用定点数 95 | ``` 96 | Fix64 a = (Fix64)1; 97 | Fix64 b = (Fix64)2; 98 | Fix64 c = a + b; 99 | ``` 100 | 关于定点数的更多相关细节,请参看文后内容:[哪些unity数据类型不能直接使用](#哪些unity数据类型不能直接使用) 101 | 102 | 103 | ###关于dotween的正确使用 104 | 提及定点数,我们不得不关注一下项目中常用的dotween这个插件,这个插件功能强大,使用非常方便,让我们在做动画时游刃有余,但是如果放到帧同步框架中就不能随便使用了. 105 | 上面提到的浮点数精度问题有可能带来巨大的影响,而dotween的整个逻辑都是基于时间帧(Time.deltaTime)插值的,而不是基于帧定长插值,因此不能在涉及到逻辑相关的地方使用,只能用在动画动作渲染相关的地方,比如下面代码就是不能使用的 106 | ``` 107 | DoLocalMove() function() 108 | //移动到某个位置后触发会影响后续判断的逻辑 109 | m_fixMoveTime = Fix64.Zero; 110 | end 111 | ``` 112 | 如果只是渲染表现,而与逻辑运算无关的地方,则可以继续使用dotween. 113 | 我们整个帧框架的逻辑运算中没有物理时间的概念,一旦逻辑中涉及到真实物理时间,那肯定会对最终计算的结果造成不可预计的影响,因此类似dotween等动画插件在使用时需要我们多加注意,一个疏忽就会带来整个逻辑运算结果的不一致. 114 | 115 | ##随机数 116 | 游戏中几乎很难避免使用随机数,恰好随机数也是帧同步框架中一个需要高度关注的注意点,如果每次战斗回放产生的随机数是不一致的,那如何能保证战斗结果是一致的呢,因此我们需要对随机数进行控制,由于不同平台,不同操作系统对随机数的处理方式不同,因此我们避免使用平台自带的随机数接口,而是使用自定义的可控随机数算法SRandom.cs来替代,保证随机数的产生在跨平台方面不会出现问题.同时我们需要记录下每场战斗的随机数种子,只要确定了种子,那产生的随机数序列就一定是一致的. 117 | 部分代码片段: 118 | ``` 119 | // range:[min~(max-1)] 120 | public uint Range(uint min, uint max) 121 | { 122 | if (min > max) 123 | throw new ArgumentOutOfRangeException("minValue", string.Format("'{0}' cannot be greater than {1}.", min, max)); 124 | 125 | uint num = max - min; 126 | return Next(num) + min; 127 | } 128 | 129 | public int Next(int max) 130 | { 131 | return (int)(Next() % max); 132 | } 133 | ``` 134 | 135 | #服务器同步校验 136 | 服务器校验和同步运算在现在的游戏中应用的越来越广泛,既然要让服务器运行相关的核心代码,那么这部分客户端与服务器共用的逻辑就有一些需要注意的地方. 137 |
  • [逻辑与渲染进行分离](#逻辑和渲染如何进行分离) 138 |
  • [逻辑代码版本控制策略](#逻辑代码版本控制策略) 139 |
  • [避免直接使用Unity特定的数据类型](#哪些unity数据类型不能直接使用) 140 |
  • [避免直接调用Unity特定的接口](#哪些unity接口不能直接调用) 141 | 142 | 143 | ##逻辑和渲染如何进行分离 144 | 服务器是没有渲染的,它只能执行纯逻辑,因此我们的逻辑代码中如何做到逻辑和渲染完全分离就很重要 145 | 146 | 虽然我们在进行模式设计和代码架构的过程中会尽量做到让逻辑和渲染解耦,独立运行*(具体实现请参见sample源码),*但出于维护同一份逻辑代码的考量,我们并没有办法完全把部分逻辑代码进行隔离,因此怎么识别当前运行环境是客户端还是服务器就很必要了 147 | 148 | unity给我们提供了自定义宏定义开关的方法,我们可以通过这个开关来判断当前运行平台是否为客户端,同时关闭服务器代码中不需要执行的渲染部分 149 | ![](https://img-blog.csdn.net/20180827093710584?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbnppMjE1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 150 | 我们可以在unity中Build Settings--Player Settings--Other Settings中找到**Scripting Define Symbols**选项,在其中填入 151 | ``` 152 | _CLIENTLOGIC_ 153 | ``` 154 | 宏定义开关,这样在unity中我们便可以此作为是否为客户端逻辑的判断,在客户端中打开与渲染相关的代码,同时也让服务器逻辑不会受到与渲染相关逻辑的干扰,比如: 155 | ``` 156 | #if _CLIENTLOGIC_ 157 | m_gameObject.transform.localPosition = position.ToVector3(); 158 | #endif 159 | ``` 160 | 161 | 162 | ##逻辑代码版本控制策略 163 |
  • 版本控制: 164 | 同步校验的关键在于客户端服务器执行的是完全同一份逻辑源码,我们应该极力避免源码来回拷贝的情况出现,因此如何进行版本控制也是需要策略的,在我们公司项目中,需要服务器和客户端同时运行的代码是以git子模块的形式进行管理的,双端各自有自己的业务逻辑,但子模块是相同的,这样维护起来就很方便,推荐大家尝试. 165 | 166 |
  • 不同服务器架构如何适配: 167 | 客户端是c#语言写的,如果服务器也是采用的c#语言,那正好可以无缝结合,共享逻辑,但目前采用c#作为游戏服务器主要语言的项目其实很少,大多是java,c++,golang等,比如我们公司用的是skynet,如果是这种不同语言架构的环境,那我们就需要单独搭建一个c#服务器了,目前我们的做法是在fedora下结合mono搭建的战斗校验服务器,网关收到战斗校验请求后会转发到校验服务器进行战斗校验,把校验结果返回给客户端,具体的方式请参阅后文:[战斗校验服务器简单搭建指引](#战斗校验服务器简单搭建指引) 168 | 169 | 170 | ##哪些unity数据类型不能直接使用 171 |
  • float 172 |
  • Vector2 173 |
  • Vector3 174 | 上面这三种类型由于都涉及到浮点数,会让逻辑运行结果不可控,因此都不能在帧同步相关的逻辑代码中直接使用,用于替代的是在Fix64.cs中定义的定点数类型: 175 | | 原始数据类型 | 替代数据类型| 176 | | :-------- | --------:| 177 | | float | Fix64 | 178 | | Vector2 | FixVector2 | 179 | | Vector3 | FixVector3 | 180 | 181 | 182 | 同时还有一种例外的情况,某些情况下我们会用Vector2来存放int型对象,在客户端这是没问题的,因为int对象不存在精度误差问题,但是遗憾的是服务器并无法识别Vector2这个unity中的内置数据类型,因此我们不能直接调用,而是需要自己构建一个类似的数据类型,让构建后的数据类型能够跨平台. 183 | 在Fix64.cs中新增了NormalVector2这个数据类型用于替代这些unity原生的数据类型,这样就可以同时在客户端和服务器两端运行同样的逻辑代码了. 184 | 那项目中是不是完全没有float,没有Vector3这些类型了呢,其实也不完全是,比如设置颜色等API调用还是需要使用float的: 185 | ``` 186 | public void setColor(float r, float g, float b) 187 | { 188 | #if _CLIENTLOGIC_ 189 | m_gameObject.GetComponent().color = new Color(r, g, b, 1); 190 | #endif 191 | } 192 | ``` 193 | >鉴于项目中既存在浮点数数据类型也存在定点数数据类型,因此在框架中使用了匈牙利命名法进行区分,让所有参与编码的人员能一眼分辨出当前变量是浮点数还是定点数 194 | 195 | ``` 196 | Fix64 m_fixElapseTime = Fix64.Zero; //前缀fix代表该变量为Fix64类型 197 | public FixVector3 m_fixv3LogicPosition = new FixVector3(Fix64.Zero, Fix64.Zero, Fix64.Zero); //前缀fixv3代表该变量为FixVector3类型 198 | float fTime = 0; //前缀f代表该变量为float类型 199 | ``` 200 | 201 | 202 | ##哪些unity接口不能直接调用 203 | unity中某些特有的接口不能直接调用,因为服务器环境下并没有这些接口,最常见接口有以下几种: 204 |
  • Debug.Log 205 |
  • PlayerPrefs 206 |
  • Time 207 | 不能直接调用不代表不能用,框架中对这些常用接口封装到UnityTools.cs,并用上文提到的____CLIENTLOGIC____开关进行控制, 208 | ``` 209 | public static void Log(object message) 210 | { 211 | #if _CLIENTLOGIC_ 212 | UnityEngine.Debug.Log(message); 213 | #else 214 | System.Console.WriteLine (message); 215 | #endif 216 | } 217 | 218 | public static void playerPrefsSetString(string key, string value) 219 | { 220 | #if _CLIENTLOGIC_ 221 | PlayerPrefs.SetString(key, value); 222 | #endif 223 | } 224 | ``` 225 | 这样在逻辑代码中调用UnityTools中的接口就可以实现跨平台了 226 | ``` 227 | UnityTools.Log("end logic frame: " + GameData.g_uGameLogicFrame); 228 | ``` 229 | 230 | #加速功能 231 | 实现了基础的帧同步核心功能后,加速功能就很容易实现了,我们只需要改变Time.timeScale这个系统阀值就可以实现. 232 | ``` 233 | //调整战斗速度 234 | btnAdjustSpeed.onClick.AddListener(delegate () 235 | { 236 | if (Time.timeScale == 1) 237 | { 238 | Time.timeScale = 2; 239 | txtAdjustSpeed.text = "2倍速"; 240 | } 241 | else if (Time.timeScale == 2) 242 | { 243 | Time.timeScale = 4; 244 | txtAdjustSpeed.text = "4倍速"; 245 | } 246 | else if (Time.timeScale == 4) 247 | { 248 | Time.timeScale = 1; 249 | txtAdjustSpeed.text = "1倍速"; 250 | } 251 | }); 252 | ``` 253 | 需要注意的是,由于帧同步的核心原理是在单元片段时间内执行完全相同次数的逻辑运算,从而保证相同输入的结果一定一致,因此在加速后,物理时间内的计算量跟加速的倍数成正比,同样的1秒物理时间片段,加速两倍的计算量是不加速的两倍,加速10倍的运算量是不加速的10倍,因此我们会发现一些性能比较差的设备在加速后会出现明显的卡顿和跳帧的状况,这是CPU运算超负荷的表现,因此需要根据游戏实际的运算量和表现来确定最大加速倍数,以免加速功能影响游戏体验 254 | ##小谈加速优化 255 | 实际项目中很容易存在加速后卡顿的问题,这是硬件机能决定的,因此如何在加速后进行优化就很重要,最常见的做法是优化美术效果,把一些不太重要的特效,比如打击效果,buff效果等暂时关掉,加速后会导致各种特效的频繁创建和销毁,开销极大,并且加速后很多细节本来就很难看清楚了,因此根据加速的等级选择性的屏蔽掉一些不影响游戏品质的特效是个不错的思路.由此思路可以引申出一些类似的优化策略,比如停止部分音效的播放,屏蔽实时阴影等小技巧. 256 | 257 | #战斗回放功能 258 | 通过上面的基础框架的搭建,我们确保了相同的输入一定得到相同的结果,那么战斗回放的问题也就变得相对简单了,我们只需要记录在某个关键游戏帧触发了什么事件就可以了,比如在第100游戏帧,150游戏帧分别触发了**出兵**事件,那我们在回放的时候进行判断,当游戏逻辑帧运行到这两个关键帧时,即调用出兵的API,还原出兵操作,由于操作一致结果必定一致,因此我们就可以看到与原始战斗过程完全一致的战斗回放了. 259 | ##记录战斗关键事件 260 | 1.在战斗过程中实时记录 261 | ``` 262 | GameData.battleInfo info = new GameData.battleInfo(); 263 | info.uGameFrame = GameData.g_uGameLogicFrame; 264 | info.sckeyEvent = "createSoldier"; 265 | GameData.g_listUserControlEvent.Add(info); 266 | ``` 267 | 2.战斗结束后根据战斗过程中实时记录的信息进行统一保存 268 | ``` 269 | //- 记录战斗信息(回放时使用) 270 | // 271 | // @return none 272 | void recordBattleInfo() { 273 | if (false == GameData.g_bRplayMode) { 274 | //记录战斗数据 275 | string content = ""; 276 | for (int i = 0; i < GameData.g_listUserControlEvent.Count; i++) 277 | { 278 | GameData.battleInfo v = GameData.g_listUserControlEvent[i]; 279 | //出兵 280 | if (v.sckeyEvent == "createSoldier") { 281 | content += v.uGameFrame + "," + v.sckeyEvent + "$"; 282 | } 283 | } 284 | 285 | UnityTools.playerPrefsSetString("battleRecord", content); 286 | GameData.g_listUserControlEvent.Clear(); 287 | } 288 | } 289 | ``` 290 | >Sample为了精简示例流程,战斗日志采用字符串进行存储,用'$'等作为切割标识符,实际项目中可根据实际的网络协议进行制定,比如protobuff,sproto等 291 | 292 | ##复原战斗事件 293 | 1.把战斗过程中保存的战斗事件进行解码: 294 | ``` 295 | //- 读取玩家的操作信息 296 | // 297 | // @return none 298 | void loadUserCtrlInfo() 299 | { 300 | GameData.g_listPlaybackEvent.Clear(); 301 | 302 | string content = battleRecord; 303 | 304 | string[] contents = content.Split('$'); 305 | 306 | for (int i = 0; i < contents.Length - 1; i++) 307 | { 308 | string[] battleInfo = contents[i].Split(','); 309 | 310 | GameData.battleInfo info = new GameData.battleInfo(); 311 | 312 | info.uGameFrame = int.Parse(battleInfo[0]); 313 | info.sckeyEvent = battleInfo[1]; 314 | 315 | GameData.g_listPlaybackEvent.Add(info); 316 | } 317 | } 318 | ``` 319 | 320 | 2.根据解码出来的事件进行逻辑复原: 321 | ``` 322 | //- 检测回放事件 323 | // 如果有回放事件则进行回放 324 | // @param gameFrame 当前的游戏帧 325 | // @return none 326 | void checkPlayBackEvent(int gameFrame) 327 | { 328 | if (GameData.g_listPlaybackEvent.Count > 0) { 329 | for (int i = 0; i < GameData.g_listPlaybackEvent.Count; i++) 330 | { 331 | GameData.battleInfo v = GameData.g_listPlaybackEvent[i]; 332 | 333 | if (gameFrame == v.uGameFrame) { 334 | if (v.sckeyEvent == "createSoldier") { 335 | createSoldier(); 336 | } 337 | } 338 | } 339 | } 340 | } 341 | ``` 342 | 343 | #框架文件结构 344 | ![](https://img-blog.csdn.net/20180901094353592?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbnppMjE1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 345 | 整个框架中最核心的代码为LockStepLogic.cs(帧同步逻辑),Fix64.cs(定点数)和SRandom.cs(随机数) 346 | 其余代码作为一个示例,如何把核心代码运用于实际项目中,并且展示了一个稍微复杂的逻辑如何在帧同步框架下良好运行. 347 |
  • **battle**目录下为帧同步逻辑以及战斗相关的核心代码 348 |
  • **battle/core**为战斗核心代码,其中 349 | -**action**为自己实现的移动,延迟等基础事件 350 | -**base**为基础对象,所有战场可见的物体都继承自基础对象 351 | -**soldier**为士兵相关 352 | -**state**为状态机相关 353 | -**tower**为塔相关 354 |
  • **ui**为战斗UI 355 |
  • **view**为视图相关 356 | 357 | #自带sample流程 358 | 流程:战斗---战斗结束提交操作步骤进行服务器校验---接收服务器校验结果---记录战斗日志---进行战斗回放 359 | ![](https://img-blog.csdn.net/201808270908120?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbnppMjE1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 360 |
  • 绿色部分为完全相同的战斗逻辑 361 |
  • 蓝色部分为完全相同的用户输入 362 | 363 | 364 | >示例sample中加入了一个非常简单的socket通信功能,用于将客户端的操作发送给服务器,服务器根据客户端的操作进行瞬时回放运算,然后将运算结果发还给客户端进行比对,这里只做了一个最简单的socket功能,力求让整个sample最精简化,实际项目中可根据原有的服务器架构进行替换. 365 | 366 |
    367 |
    368 | 369 | #战斗校验服务器简单搭建指引 370 |
  • 安装mono环境 371 |
  • 编译可执行文件 372 |
  • 实现简单socket通信回传 373 | 374 | ##安装mono环境 375 | 进入官网https://www.mono-project.com/download/stable/#download-lin-fedora 376 | 按照指引进行安装即可 377 | 378 | ##编译可执行文件 379 | 1.打开刚才安装好的monodeveloper 380 | 2.点击file->new->solution 381 | 3.在左侧的选项卡中选择Other->.NET 382 | 4.在右侧General下选择Console Project 383 | ![](https://img-blog.csdn.net/20180830093255771?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbnppMjE1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 384 | 在左侧工程名上右键导入子模块中battle文件夹下的所有源码 385 | ![](https://img-blog.csdn.net/2018083009331084?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbnppMjE1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 386 | 点击build->Rebuild All,如果编译通过这时会在工程目录下的obj->x86->Debug文件夹下生成可执行文件 387 | 如果编译出错请回看上文提到的各种注意点,排查哪里出了问题. 388 | >开发过程中发现工程目录下如果存在git相关的文件会导致monodeveloper报错关闭,如果遇到这种情况需要将工程目录下的.git文件夹和.gitmodules文件进行删除,然后即可正常编译了. 389 | 390 | ##运行可执行文件 391 | cmd打开命令行窗口,切换到刚才编译生成的Debug文件目录下,通过mono命令运行编译出来的exe可执行文件 392 | ``` 393 | mono LockStepFrameWork-server.exe 394 | ``` 395 | ##服务器端战斗校验逻辑 396 | 可执行文件生成后并没有什么实际用处,因为还没有跟我们的战斗逻辑发生联系,我们需要进行一些小小的修改让验证逻辑起作用. 397 | 修改新建工程自动生成的Program.cs文件,加入验证代码 398 | ``` 399 | BattleLogic battleLogic = new BattleLogic (); 400 | battleLogic.init (); 401 | battleLogic.setBattleRecord (battleRecord); 402 | battleLogic.replayVideo(); 403 | 404 | while (true) { 405 | battleLogic.updateLogic(); 406 | if (battleLogic.m_bIsBattlePause) { 407 | break; 408 | } 409 | } 410 | Console.WriteLine("m_uGameLogicFrame: " + BattleLogic.s_uGameLogicFrame); 411 | ``` 412 | 通过上述代码我们可以看到,首先构建了一个BattleLogic对象,然后传入客户端传过来的操作日志(battleRecord),然后用一个while循环在极短的时间内把战斗逻辑运算了一次,当判断到m_bIsBattlePause为true时证明战斗已结束. 413 | 那么我们最后以什么作为战斗校验是否通过的衡量指标呢?很简单,通过游戏逻辑帧s_uGameLogicFrame来进行判断就很准确了,因为只要有一丁点不一致,都不可能跑出完全相同的逻辑帧数,如果想要更保险一点,还可以加入别的与游戏业务逻辑具体相关的参数进行判断,比如杀死的敌人个数,发射了多少颗子弹等等合并作为综合判断依据. 414 | 415 | ##实现简单socket通信回传 416 | 光有战斗逻辑校验还不够,我们需要加入服务器监听,接收客户端发送过来的战斗日志,计算出结果后再回传给客户端,框架只实现了一段很简单的socket监听和回发消息的功能(尽量将网络通信流程简化,因为大家肯定有自己的一套网络框架和协议),具体请参看Sample源码. 417 | ``` 418 | Socket serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); 419 | IPAddress ip = IPAddress.Any; 420 | IPEndPoint point = new IPEndPoint(ip, 2333); 421 | //socket绑定监听地址 422 | serverSocket.Bind(point); 423 | Console.WriteLine("Listen Success"); 424 | //设置同时连接个数 425 | serverSocket.Listen(10); 426 | 427 | //利用线程后台执行监听,否则程序会假死 428 | Thread thread = new Thread(Listen); 429 | thread.IsBackground = true; 430 | thread.Start(serverSocket); 431 | 432 | Console.Read(); 433 | ``` 434 | 435 | #框架源码 436 | ##客户端 437 | https://github.com/CraneInForest/LockStepSimpleFramework-Client.git 438 | 439 | ##服务器 440 | https://github.com/CraneInForest/LockStepSimpleFramework-Server.git 441 | 442 | ##客户端服务器共享逻辑 443 | https://github.com/CraneInForest/LockStepSimpleFramework-Shared.git 444 | >共享逻辑以子模块的形式分别加入到客户端和服务器中,如要运行源码请在clone完毕主仓库后再更新一下子模块,否则没有共享逻辑是无法通过编译的 445 | 446 | 子模块更新命令: 447 | ``` 448 | git submodule update --init --recursive 449 | ``` 450 | 451 | >编译环境: 452 | >客户端:win10 + unity5.5.6f1 453 | >服务器:fedora27 64-bit 454 | 455 | -------------------------------------------------------------------------------- /UnityTools.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: unity相关功能封装 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 03/7/2018 6 | // 7 | // 8 | // 9 | 10 | #if _CLIENTLOGIC_ 11 | using UnityEngine; 12 | #endif 13 | using System.Collections; 14 | 15 | public class UnityTools { 16 | public static string playerPrefsGetString(string key) 17 | { 18 | #if _CLIENTLOGIC_ 19 | return PlayerPrefs.GetString(key); 20 | #else 21 | return ""; 22 | #endif 23 | } 24 | 25 | public static void playerPrefsSetString(string key, string value) 26 | { 27 | #if _CLIENTLOGIC_ 28 | PlayerPrefs.SetString(key, value); 29 | #endif 30 | } 31 | 32 | public static void setTimeScale(float value) 33 | { 34 | #if _CLIENTLOGIC_ 35 | Time.timeScale = value; 36 | #endif 37 | } 38 | 39 | public static void Log(object message) 40 | { 41 | #if _CLIENTLOGIC_ 42 | UnityEngine.Debug.Log(message); 43 | #else 44 | System.Console.WriteLine (message); 45 | #endif 46 | } 47 | 48 | public static void LogError(object message) 49 | { 50 | #if _CLIENTLOGIC_ 51 | Debug.LogError(message); 52 | #endif 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /UnityTools.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2e22d7f5bdadfdd46ba48131df75af03 3 | timeCreated: 1534571796 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 00db84a7b5a08074b846a47a824918a3 3 | folderAsset: yes 4 | timeCreated: 1534571796 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /core/BattleLogic.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 战斗主逻辑 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class BattleLogic 12 | { 13 | //是否暂停(进入结算界面时, 战斗逻辑就会暂停) 14 | public bool m_bIsBattlePause = true; 15 | 16 | //帧同步核心逻辑对象 17 | LockStepLogic m_lockStepLogic = null; 18 | 19 | //战斗日志 20 | string battleRecord = ""; 21 | 22 | //游戏逻辑帧 23 | static public int s_uGameLogicFrame = 0; 24 | 25 | //是否已开战 26 | private bool m_bFireWar = false; 27 | 28 | //- 主循环 29 | // Some description, can be over several lines. 30 | // @return value description. 31 | // @author 32 | public void updateLogic() { 33 | //如果战斗逻辑暂停则不再运行 34 | if (m_bIsBattlePause) { 35 | return; 36 | } 37 | 38 | //调用帧同步逻辑 39 | m_lockStepLogic.updateLogic(); 40 | } 41 | 42 | //- 战斗逻辑 43 | // 44 | // @return none 45 | public void frameLockLogic() 46 | { 47 | //如果是回放模式 48 | if (GameData.g_bRplayMode) 49 | { 50 | //检测回放事件 51 | checkPlayBackEvent(GameData.g_uGameLogicFrame); 52 | } 53 | 54 | recordLastPos(); 55 | 56 | //动作管理器update 57 | GameData.g_actionMainManager.updateLogic(); 58 | 59 | //塔 60 | for (int i = 0; i < GameData.g_listTower.Count; i++) 61 | { 62 | GameData.g_listTower[i].updateLogic(); 63 | } 64 | 65 | //子弹 66 | for (int i = 0; i < GameData.g_listBullet.Count; i++) 67 | { 68 | GameData.g_listBullet[i].updateLogic(); 69 | } 70 | 71 | //士兵 72 | for (int i = 0; i < GameData.g_listSoldier.Count; i++) 73 | { 74 | GameData.g_listSoldier[i].updateLogic(); 75 | } 76 | 77 | if (m_bFireWar && GameData.g_listSoldier.Count == 0) 78 | { 79 | stopBattle(); 80 | } 81 | } 82 | 83 | //- 记录最后的位置 84 | // 85 | // @return none. 86 | void recordLastPos() 87 | { 88 | //子弹 89 | for (int i = 0; i < GameData.g_listBullet.Count; i++) { 90 | GameData.g_listBullet[i].recordLastPos(); 91 | } 92 | 93 | //士兵 94 | for (int i = 0; i < GameData.g_listSoldier.Count; i++) 95 | { 96 | GameData.g_listSoldier[i].recordLastPos(); 97 | } 98 | } 99 | 100 | //- 更新各种对象绘制的位置 101 | // 包括怪,子弹等等,因为塔的位置是固定的,所以不需要实时刷新塔的位置,提升效率 102 | // @return none 103 | public void updateRenderPosition(float interpolation) 104 | { 105 | //子弹 106 | for (int i = 0; i 136 | /// 开始战斗 137 | /// 138 | /// 139 | public void startBattle() 140 | { 141 | GameData.g_srand = new SRandom(1000); 142 | m_bIsBattlePause = false; 143 | m_lockStepLogic.init(); 144 | 145 | //读取玩家操作数据,为回放做准备 146 | if (GameData.g_bRplayMode) 147 | { 148 | loadUserCtrlInfo(); 149 | } 150 | 151 | GameData.g_uGameLogicFrame = 0; 152 | TowerStandState.s_fixTestCount = (Fix64)0; 153 | TowerStandState.s_scTestContent = ""; 154 | 155 | //创建塔 156 | createTowers(); 157 | } 158 | 159 | /// 160 | /// 停止战斗 161 | /// 162 | /// 163 | public void stopBattle() 164 | { 165 | UnityTools.Log("end logic frame: " + GameData.g_uGameLogicFrame); 166 | UnityTools.Log("s_fixTestCount: " + TowerStandState.s_fixTestCount); 167 | UnityTools.Log("s_scTestContent: " + TowerStandState.s_scTestContent); 168 | 169 | m_bFireWar = false; 170 | s_uGameLogicFrame = GameData.g_uGameLogicFrame; 171 | 172 | //记录关键事件 173 | if (!GameData.g_bRplayMode) 174 | { 175 | GameData.battleInfo info = new GameData.battleInfo(); 176 | info.uGameFrame = GameData.g_uGameLogicFrame; 177 | info.sckeyEvent = "stopBattle"; 178 | GameData.g_listUserControlEvent.Add(info); 179 | } 180 | 181 | gameEnd(); 182 | } 183 | 184 | /// 185 | /// 回放战斗录像 186 | /// 187 | /// 188 | public void replayVideo() 189 | { 190 | GameData.g_bRplayMode = true; 191 | GameData.g_uGameLogicFrame = 0; 192 | startBattle(); 193 | } 194 | 195 | /// 196 | /// 创建塔 197 | /// 198 | /// 199 | private void createTowers() 200 | { 201 | for (int i = 0; i < 5; i++) 202 | { 203 | var tower = GameData.g_towerFactory.createTower(); 204 | tower.m_fixv3LogicPosition = new FixVector3((Fix64)5, (Fix64)1.3f, (Fix64)(-3.0f) + (Fix64)2.5f * i); 205 | tower.updateRenderPosition(0); 206 | } 207 | } 208 | 209 | /// 210 | /// 创建士兵 211 | /// 212 | /// 213 | public void createSoldier() 214 | { 215 | m_bFireWar = true; 216 | 217 | var soldier = GameData.g_soldierFactory.createSoldier(); 218 | soldier.m_fixv3LogicPosition = new FixVector3((Fix64)0, (Fix64)1, (Fix64)(-4.0f)); 219 | soldier.updateRenderPosition(0); 220 | 221 | float moveSpeed = 3 + GameData.g_srand.Range(0, 3); 222 | soldier.moveTo(soldier.m_fixv3LogicPosition, new FixVector3(soldier.m_fixv3LogicPosition.x, soldier.m_fixv3LogicPosition.y, (Fix64)8), (Fix64)moveSpeed); 223 | 224 | //记录关键事件 225 | if (!GameData.g_bRplayMode) 226 | { 227 | GameData.battleInfo info = new GameData.battleInfo(); 228 | info.uGameFrame = GameData.g_uGameLogicFrame; 229 | info.sckeyEvent = "createSoldier"; 230 | GameData.g_listUserControlEvent.Add(info); 231 | } 232 | } 233 | 234 | //- 读取玩家的操作信息 235 | // 236 | // @return none 237 | void loadUserCtrlInfo() 238 | { 239 | GameData.g_listPlaybackEvent.Clear(); 240 | 241 | string content = battleRecord; 242 | 243 | string[] contents = content.Split('$'); 244 | 245 | for (int i = 0; i < contents.Length - 1; i++) 246 | { 247 | string[] battleInfo = contents[i].Split(','); 248 | 249 | GameData.battleInfo info = new GameData.battleInfo(); 250 | 251 | info.uGameFrame = int.Parse(battleInfo[0]); 252 | info.sckeyEvent = battleInfo[1]; 253 | 254 | GameData.g_listPlaybackEvent.Add(info); 255 | } 256 | } 257 | 258 | //- 检测回放事件 259 | // 如果有回放事件则进行回放 260 | // @param gameFrame 当前的游戏帧 261 | // @return none 262 | void checkPlayBackEvent(int gameFrame) 263 | { 264 | if (GameData.g_listPlaybackEvent.Count > 0) { 265 | for (int i = 0; i < GameData.g_listPlaybackEvent.Count; i++) 266 | { 267 | GameData.battleInfo v = GameData.g_listPlaybackEvent[i]; 268 | 269 | if (gameFrame == v.uGameFrame) { 270 | if (v.sckeyEvent == "createSoldier") { 271 | createSoldier(); 272 | } 273 | } 274 | } 275 | } 276 | } 277 | 278 | //- 暂停战斗逻辑 279 | // 280 | // @return none. 281 | void pauseBattleLogic() 282 | { 283 | m_bIsBattlePause = true; 284 | } 285 | 286 | public void gameEnd() 287 | { 288 | if (!m_bIsBattlePause) 289 | { 290 | //UnityTools.setTimeScale(1); 291 | 292 | //销毁战场上的所有对象 293 | //塔 294 | for (int i = GameData.g_listTower.Count - 1; i >= 0; i--) 295 | { 296 | GameData.g_listTower[i].killSelf(); 297 | } 298 | 299 | //子弹 300 | for (int i = GameData.g_listBullet.Count - 1; i >= 0; i--) 301 | { 302 | GameData.g_listBullet[i].killSelf(); 303 | } 304 | 305 | //士兵 306 | for (int i = GameData.g_listSoldier.Count - 1; i >= 0; i--) 307 | { 308 | GameData.g_listSoldier[i].killSelf(); 309 | } 310 | 311 | if (!GameData.g_bRplayMode) { 312 | recordBattleInfo(); 313 | #if _CLIENTLOGIC_ 314 | SimpleSocket socket = new SimpleSocket(); 315 | socket.Init(); 316 | socket.sendBattleRecordToServer(UnityTools.playerPrefsGetString("battleRecord")); 317 | #endif 318 | } 319 | 320 | pauseBattleLogic(); 321 | 322 | GameData.g_bRplayMode = false; 323 | 324 | GameData.release(); 325 | } 326 | } 327 | 328 | //- 记录战斗信息(回放时使用) 329 | // 330 | // @return none 331 | void recordBattleInfo() { 332 | if (false == GameData.g_bRplayMode) { 333 | 334 | //记录战斗数据 335 | string content = ""; 336 | for (int i = 0; i < GameData.g_listUserControlEvent.Count; i++) 337 | { 338 | GameData.battleInfo v = GameData.g_listUserControlEvent[i]; 339 | //出兵 340 | if (v.sckeyEvent == "createSoldier") { 341 | content += v.uGameFrame + "," + v.sckeyEvent + "$"; 342 | } 343 | } 344 | 345 | UnityTools.playerPrefsSetString("battleRecord", content); 346 | GameData.g_listUserControlEvent.Clear(); 347 | } 348 | } 349 | 350 | public void setBattleRecord(string record) 351 | { 352 | battleRecord = record; 353 | } 354 | 355 | //- 释放资源 356 | // 357 | // @return none 358 | void release() 359 | { 360 | 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /core/BattleLogic.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4787c189f8a4327479467003e38d7b72 3 | timeCreated: 1524554022 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/GameData.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 公用数据类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 03/7/2018 6 | // 7 | // 8 | // 9 | 10 | 11 | using System.Collections; 12 | using System.Collections.Generic; 13 | using System.IO; 14 | 15 | public class GameData { 16 | //所有士兵的队列 17 | public static List g_listSoldier = new List(); 18 | 19 | //所有塔的队列 20 | public static List g_listTower = new List(); 21 | 22 | //所有子弹的队列 23 | public static List g_listBullet = new List(); 24 | 25 | //所有操作事件的队列 26 | public static List g_listUserControlEvent = new List(); 27 | 28 | //所有回放事件的队列 29 | public static List g_listPlaybackEvent = new List(); 30 | 31 | //预定的每帧的时间长度 32 | public static Fix64 g_fixFrameLen = Fix64.FromRaw(273); 33 | 34 | //游戏的逻辑帧 35 | public static int g_uGameLogicFrame = 0; 36 | 37 | //是否为回放模式 38 | public static bool g_bRplayMode = false; 39 | 40 | //士兵工厂 41 | public static SoldierFactory g_soldierFactory = new SoldierFactory(); 42 | 43 | //塔工厂 44 | public static TowerFactory g_towerFactory = new TowerFactory(); 45 | 46 | //action主管理器(用于管理各liveobject内部的独立actionManager) 47 | public static ActionMainManager g_actionMainManager = new ActionMainManager(); 48 | 49 | //子弹管理器 50 | public static BulletFactory g_bulletManager = new BulletFactory(); 51 | 52 | //战斗是否结束 53 | public static bool g_bBattleEnd = false; 54 | 55 | //随机数对象 56 | public static SRandom g_srand = new SRandom(1000); 57 | 58 | public struct battleInfo 59 | { 60 | public int uGameFrame; 61 | public string sckeyEvent; 62 | } 63 | 64 | //- 释放资源 65 | // 66 | // @return none 67 | public static void release() { 68 | g_listPlaybackEvent.Clear(); 69 | 70 | g_listUserControlEvent.Clear(); 71 | 72 | GameData.g_actionMainManager.release(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /core/GameData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c4d072c74fc732f47a51da83143b74cf 3 | timeCreated: 1524552139 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/LockStepLogic.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 帧同步核心逻辑 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | using System; 11 | public class LockStepLogic { 12 | //累计运行的时间 13 | float m_fAccumilatedTime = 0; 14 | 15 | //下一个逻辑帧的时间 16 | float m_fNextGameTime = 0; 17 | 18 | //预定的每帧的时间长度 19 | float m_fFrameLen; 20 | 21 | //挂载的逻辑对象 22 | BattleLogic m_callUnit = null; 23 | 24 | //两帧之间的时间差 25 | float m_fInterpolation = 0; 26 | 27 | public LockStepLogic() 28 | { 29 | init(); 30 | } 31 | 32 | public void init() 33 | { 34 | m_fFrameLen = (float)GameData.g_fixFrameLen; 35 | 36 | m_fAccumilatedTime = 0; 37 | 38 | m_fNextGameTime = 0; 39 | 40 | m_fInterpolation = 0; 41 | } 42 | 43 | public void updateLogic() { 44 | float deltaTime = 0; 45 | #if _CLIENTLOGIC_ 46 | deltaTime = UnityEngine.Time.deltaTime; 47 | #else 48 | deltaTime = 0.1f; 49 | #endif 50 | 51 | /**************以下是帧同步的核心逻辑*********************/ 52 | m_fAccumilatedTime = m_fAccumilatedTime + deltaTime; 53 | 54 | //如果真实累计的时间超过游戏帧逻辑原本应有的时间,则循环执行逻辑,确保整个逻辑的运算不会因为帧间隔时间的波动而计算出不同的结果 55 | while (m_fAccumilatedTime > m_fNextGameTime) { 56 | 57 | //运行与游戏相关的具体逻辑 58 | m_callUnit.frameLockLogic(); 59 | 60 | //计算下一个逻辑帧应有的时间 61 | m_fNextGameTime += m_fFrameLen; 62 | 63 | //游戏逻辑帧自增 64 | GameData.g_uGameLogicFrame += 1; 65 | } 66 | 67 | //计算两帧的时间差,用于运行补间动画 68 | m_fInterpolation = (m_fAccumilatedTime + m_fFrameLen - m_fNextGameTime) / m_fFrameLen; 69 | 70 | //更新绘制位置 71 | m_callUnit.updateRenderPosition(m_fInterpolation); 72 | /**************帧同步的核心逻辑完毕*********************/ 73 | } 74 | 75 | //- 设置调用的宿主 76 | // 77 | // @param unit 调用的宿主 78 | // @return none 79 | public void setCallUnit(BattleLogic unit){ 80 | m_callUnit = unit; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /core/LockStepLogic.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7c1d2198050c5dc49909ac0c2839321b 3 | timeCreated: 1524554034 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/SRandom.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 自定义随机数 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 03/7/2018 6 | // 7 | // 8 | // 9 | 10 | using System; 11 | public class SRandom 12 | { 13 | public static int count = 0; 14 | 15 | ulong randSeed = 1; 16 | public SRandom(uint seed) 17 | { 18 | randSeed = seed; 19 | } 20 | 21 | public uint Next() 22 | { 23 | randSeed = randSeed * 1103515245 + 12345; 24 | return (uint)(randSeed / 65536); 25 | } 26 | 27 | // range:[0 ~(max-1)] 28 | public uint Next(uint max) 29 | { 30 | return Next() % max; 31 | } 32 | 33 | // range:[min~(max-1)] 34 | public uint Range(uint min, uint max) 35 | { 36 | if (min > max) 37 | throw new ArgumentOutOfRangeException("minValue", string.Format("'{0}' cannot be greater than {1}.", min, max)); 38 | 39 | uint num = max - min; 40 | return Next(num) + min; 41 | } 42 | 43 | public int Next(int max) 44 | { 45 | return (int)(Next() % max); 46 | } 47 | 48 | public int Range(int min, int max) 49 | { 50 | count++; 51 | 52 | if (min > max) 53 | throw new ArgumentOutOfRangeException("minValue", string.Format("'{0}' cannot be greater than {1}.", min, max)); 54 | 55 | int num = max - min; 56 | 57 | return Next(num) + min; 58 | } 59 | 60 | public Fix64 Range(Fix64 min, Fix64 max) 61 | { 62 | if (min > max) 63 | throw new ArgumentOutOfRangeException("minValue", string.Format("'{0}' cannot be greater than {1}.", min, max)); 64 | 65 | uint num = (uint)(max.RawValue - min.RawValue); 66 | return Fix64.FromRaw(Next(num) + min.RawValue); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /core/SRandom.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1639187dd022f084389aa51c603dc7f6 3 | timeCreated: 1524897019 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/action.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87b31e320da0a82408ad54d7cd297c4c 3 | folderAsset: yes 4 | timeCreated: 1524552126 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /core/action/ActionMainManager.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 事件管理主类(统管所有的actionManager) 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | 10 | using System.Collections; 11 | using System.Collections.Generic; 12 | 13 | public class ActionMainManager { 14 | 15 | List m_listActionMain = new List(); 16 | 17 | public void updateLogic() 18 | { 19 | for (int i = 0; i < m_listActionMain.Count; i++) 20 | { 21 | if (m_listActionMain[i].enable) 22 | { 23 | m_listActionMain[i].updateLogic(); 24 | } 25 | } 26 | 27 | for (int i = m_listActionMain.Count - 1; i >= 0; i--) 28 | { 29 | if (!m_listActionMain[i].enable) 30 | { 31 | m_listActionMain.Remove(m_listActionMain[i]); 32 | } 33 | } 34 | } 35 | 36 | public void addActionManager(ActionManager actionManager) 37 | { 38 | m_listActionMain.Add(actionManager); 39 | } 40 | 41 | public void removeActionManager(ActionManager actionManager) 42 | { 43 | actionManager.enable = false; 44 | } 45 | 46 | public void release() 47 | { 48 | for (int i = m_listActionMain.Count - 1; i >= 0; i--) 49 | { 50 | m_listActionMain.Remove(m_listActionMain[i]); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /core/action/ActionMainManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b8d2eec6c7e10b242b074202e1e7a63f 3 | timeCreated: 1524552126 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/action/ActionManager.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 事件管理类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | 10 | using System.Collections; 11 | using System.Collections.Generic; 12 | 13 | public class ActionManager{ 14 | List m_listAction = new List(); 15 | public bool m_bEnable = true; 16 | public bool enable { get { return m_bEnable; } set { m_bEnable = value; } } 17 | 18 | public void updateLogic() { 19 | for (int i = 0; i < m_listAction.Count; i++) 20 | { 21 | if (m_listAction[i].enable) { 22 | m_listAction[i].updateLogic(); 23 | } 24 | } 25 | 26 | for (int i = m_listAction.Count - 1; i >= 0; i--) 27 | { 28 | if (!m_listAction[i].enable) 29 | { 30 | m_listAction.Remove(m_listAction[i]); 31 | } 32 | } 33 | } 34 | 35 | public void addAction(BaseAction action) { 36 | m_listAction.Add(action); 37 | 38 | action.setBelongToManager(this); 39 | } 40 | 41 | public void removeAction(BaseAction action) 42 | { 43 | action.enable = false; 44 | } 45 | 46 | public void stopAllAction() { 47 | for (int i = m_listAction.Count - 1; i >= 0; i--) 48 | { 49 | m_listAction.Remove(m_listAction[i]); 50 | } 51 | } 52 | 53 | public void stopAction(string label) { 54 | for (int i = m_listAction.Count - 1; i >= 0; i--) 55 | { 56 | if (label == m_listAction[i].label) { 57 | m_listAction.Remove(m_listAction[i]); 58 | } 59 | } 60 | } 61 | 62 | public void stopActionByName(string name) { 63 | for (int i = m_listAction.Count - 1; i >= 0; i--) 64 | { 65 | if (name == m_listAction[i].name) 66 | { 67 | m_listAction.Remove(m_listAction[i]); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /core/action/ActionManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 974c395d3080df948a24cfe677ee6e08 3 | timeCreated: 1524552126 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/action/BaseAction.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: Action基类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class BaseAction { 12 | private ActionCallback m_actionCallBackFunction = null; 13 | public ActionCallback actionCallBackFunction { get { return m_actionCallBackFunction; } set { m_actionCallBackFunction = value; } } 14 | 15 | private bool m_bEnable = true; 16 | public bool enable { get { return m_bEnable; } set { m_bEnable = value; } } 17 | 18 | private string m_scLabel = ""; 19 | public string label { get { return m_scLabel; } set { m_scLabel = value; } } 20 | 21 | private string m_scName = ""; 22 | public string name { get { return m_scName; } set { m_scName = value; } } 23 | 24 | private BaseObject m_unit = null; 25 | public BaseObject unit { get { return m_unit; } set { m_unit = value; } } 26 | 27 | ActionManager m_belongToManager = null; 28 | 29 | public void setBelongToManager(ActionManager manager) 30 | { 31 | m_belongToManager = manager; 32 | } 33 | 34 | public ActionManager getBelongToManager() { 35 | return m_belongToManager; 36 | } 37 | 38 | public void removeSelfFromManager() 39 | { 40 | m_belongToManager.removeAction(this); 41 | } 42 | 43 | public void setLabel(string value) { 44 | label = value; 45 | } 46 | 47 | public virtual void updateLogic() 48 | { 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/action/BaseAction.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a3da356893169764e9385383cbc53d4b 3 | timeCreated: 1524552126 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/action/DelayDo.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 延迟执行的事件 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class DelayDo : BaseAction { 12 | 13 | Fix64 m_fixPlanTime = Fix64.Zero; 14 | Fix64 m_fixElapseTime = Fix64.Zero; 15 | 16 | public override void updateLogic() 17 | { 18 | m_fixElapseTime = m_fixElapseTime + GameData.g_fixFrameLen; 19 | 20 | if (m_fixElapseTime >= m_fixPlanTime) { 21 | removeSelfFromManager(); 22 | 23 | if (null != actionCallBackFunction) 24 | { 25 | actionCallBackFunction(); 26 | } 27 | } 28 | } 29 | 30 | public void init(Fix64 time, ActionCallback cb) 31 | { 32 | name = "delaydo"; 33 | m_fixPlanTime = time; 34 | actionCallBackFunction = cb; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/action/DelayDo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8a4fe8b8d2a75c14fb66161d0eb5be5a 3 | timeCreated: 1524552126 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/action/MoveTo.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 移动到指定位置的动作事件 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class MoveTo : BaseAction { 12 | 13 | FixVector3 m_fixv3MoveDistance = new FixVector3(Fix64.Zero, Fix64.Zero, Fix64.Zero); 14 | 15 | Fix64 m_fixMoveTime = Fix64.Zero; 16 | 17 | Fix64 m_fixMoveElpaseTime = Fix64.Zero; 18 | 19 | FixVector3 m_fixMoveStartPosition = new FixVector3(Fix64.Zero, Fix64.Zero, Fix64.Zero); 20 | 21 | FixVector3 m_fixEndPosition = new FixVector3(Fix64.Zero, Fix64.Zero, Fix64.Zero); 22 | 23 | public override void updateLogic() 24 | { 25 | bool actionOver = false; 26 | 27 | m_fixMoveElpaseTime += GameData.g_fixFrameLen; 28 | 29 | Fix64 timeScale = m_fixMoveElpaseTime / m_fixMoveTime; 30 | 31 | if (timeScale >= (Fix64)1) { 32 | timeScale = (Fix64)1; 33 | actionOver = true; 34 | } 35 | 36 | FixVector3 elpaseDistance = new FixVector3(m_fixv3MoveDistance.x * timeScale, m_fixv3MoveDistance.y * timeScale, m_fixv3MoveDistance.z * timeScale); 37 | FixVector3 newPosition = new FixVector3(m_fixMoveStartPosition.x + elpaseDistance.x, m_fixMoveStartPosition.y + elpaseDistance.y, m_fixMoveStartPosition.z + elpaseDistance.z); 38 | unit.m_fixv3LogicPosition = newPosition; 39 | 40 | if (actionOver) { 41 | removeSelfFromManager(); 42 | 43 | if (null != actionCallBackFunction){ 44 | actionCallBackFunction(); 45 | } 46 | } 47 | } 48 | 49 | public void init(BaseObject unitbody, FixVector3 startPos, FixVector3 endPos, Fix64 time, ActionCallback cb) { 50 | name = "moveto"; 51 | 52 | unit = unitbody; 53 | unit.m_fixv3LogicPosition = startPos; 54 | m_fixMoveStartPosition = startPos; 55 | m_fixEndPosition = endPos; 56 | m_fixMoveTime = time; 57 | if (m_fixMoveTime == Fix64.Zero) { 58 | m_fixMoveTime = (Fix64)0.1f; 59 | } 60 | 61 | actionCallBackFunction = cb; 62 | m_fixv3MoveDistance = new FixVector3(m_fixEndPosition.x - startPos.x, m_fixEndPosition.y - startPos.y, m_fixEndPosition.z - startPos.z); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/action/MoveTo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e39ba0812980d3e479d42d83b1defdf0 3 | timeCreated: 1524552127 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/action/MyDelegates.cs: -------------------------------------------------------------------------------- 1 |  2 | /// 3 | /// Used for action do over callbacks 4 | /// 5 | public delegate void ActionCallback(); 6 | /// 7 | /// Used for action do over callbacks 8 | /// 9 | public delegate void ActionCallback(T value); -------------------------------------------------------------------------------- /core/action/MyDelegates.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 424ab3b5951257e41970355d87c79028 3 | timeCreated: 1526282391 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/base.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 813545fd4cb155f409da35cdece1e3df 3 | folderAsset: yes 4 | timeCreated: 1524553423 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /core/base/BaseObject.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 物体对象基类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class BaseObject : UnityObject 12 | { 13 | //移动的action 14 | protected MoveTo m_movetoAction = null; 15 | public MoveTo movetoAction { get { return m_movetoAction; } set { m_movetoAction = value; } } 16 | 17 | //名字 18 | protected string m_scName = ""; 19 | public string name { get { return m_scName; } set { m_scName = value; } } 20 | 21 | //action管理器 22 | protected ActionManager m_actionManager = null; 23 | public ActionManager actionManager { get { return m_actionManager; } set { m_actionManager = value; } } 24 | 25 | protected StateMachine m_statemachine = null; 26 | public StateMachine priorAttackTarget { get { return m_statemachine; } set { m_statemachine = value; } } 27 | 28 | protected bool m_bUneffect = false; 29 | public bool uneffect { get { return m_bUneffect; } set { m_bUneffect = value; } } 30 | 31 | public BaseObject() 32 | { 33 | init(); 34 | } 35 | 36 | // Use this for initialization 37 | void init () { 38 | m_actionManager = new ActionManager(); 39 | GameData.g_actionMainManager.addActionManager(m_actionManager); 40 | } 41 | 42 | //- 移动到制定位置 43 | // 44 | // @param obj 要移动的对象 45 | // @param startPos 移动开始的位置 46 | // @param endPos 移动结束的位置 47 | // @param time 移动计划经历的时间 48 | // @param cb 移动完毕后的回调函数 49 | // @return none 50 | public void moveTo(FixVector3 startPos, FixVector3 endPos, Fix64 time, ActionCallback cb = null) 51 | { 52 | if (null == m_movetoAction) { 53 | m_movetoAction = new MoveTo(); 54 | m_movetoAction.init(this, startPos, endPos, time, cb); 55 | m_actionManager.addAction(m_movetoAction); 56 | } 57 | } 58 | 59 | //- 延迟执行方法 60 | // 61 | // @param time 要延迟的时间 62 | // @param cb 时间到了以后的回调函数 63 | // @param label 延迟动作对象的标签(用于停止延迟动作) 64 | // @return none 65 | public void delayDo(Fix64 time, ActionCallback cb, string label = null){ 66 | DelayDo delaydoAction = new DelayDo(); 67 | delaydoAction.init(time, cb); 68 | 69 | if (null != label) { 70 | delaydoAction.setLabel(label); 71 | } 72 | 73 | m_actionManager.addAction(delaydoAction); 74 | } 75 | 76 | //- 停止移动 77 | // 78 | // @return none 79 | public void stopMove(){ 80 | if (null != m_movetoAction) { 81 | m_actionManager.removeAction(m_movetoAction); 82 | 83 | m_movetoAction = null; 84 | } 85 | } 86 | 87 | //- 停止指定的action 88 | // 89 | // @param label 要停止的action的label 90 | // @return none 91 | public void stopAction(string label){ 92 | m_actionManager.stopAction(label); 93 | } 94 | 95 | //- 根据action类型停止指定的action 96 | // 97 | // @param label 要停止的action的类型 98 | // @return none 99 | public void stopActionByName(string type){ 100 | m_actionManager.stopActionByName(type); 101 | } 102 | 103 | //- 停止所有的action 104 | // 105 | // @return none 106 | public void stopAllAction(){ 107 | m_actionManager.stopAllAction(); 108 | } 109 | 110 | //- 清除action管理器 111 | // 112 | // @return none 113 | public void killActionManager(){ 114 | m_actionManager.enable = false; 115 | } 116 | 117 | 118 | //- 检测事件并执行 119 | // 由于事件都是一次性的,所以执行完毕后立即清空 120 | // @return none 121 | public void checkEvent() 122 | { 123 | //释放内存 124 | if (m_bKilled) { 125 | //停止所有delaydo 126 | stopActionByName("delaydo"); 127 | 128 | //塔 129 | if (m_scType == "tower") { 130 | 131 | for (int i = GameData.g_listTower.Count - 1; i >= 0; i--) 132 | { 133 | if (this == GameData.g_listTower[i]) 134 | { 135 | GameData.g_listTower.Remove(GameData.g_listTower[i]); 136 | break; 137 | } 138 | } 139 | 140 | } 141 | //士兵 142 | else if (m_scType == "soldier") { 143 | for (int i = GameData.g_listSoldier.Count - 1; i >= 0; i--) 144 | { 145 | if (this == GameData.g_listSoldier[i]) 146 | { 147 | GameData.g_listSoldier.Remove(GameData.g_listSoldier[i]); 148 | break; 149 | } 150 | } 151 | } 152 | //子弹 153 | else if (m_scType == "bullet") { 154 | for (int i = GameData.g_listBullet.Count - 1; i >= 0; i--) 155 | { 156 | if (this == GameData.g_listBullet[i]) 157 | { 158 | GameData.g_listBullet.Remove(GameData.g_listBullet[i]); 159 | break; 160 | } 161 | } 162 | } 163 | //其它 164 | else { 165 | UnityTools.LogError("wrong type : " + m_scType); 166 | } 167 | 168 | destroyGameObject(); 169 | } 170 | } 171 | 172 | // - 检测逻辑上是否已经死亡 173 | // 如果死亡则做对应的一系列处理 174 | // @return value description. 175 | public void checkIsDead(){ 176 | if (m_bKilled) { 177 | killSelf(); 178 | } 179 | } 180 | 181 | //-设置位置 182 | // 183 | // @param position 要设置到的位置 184 | // @return none 185 | virtual public void setPosition(FixVector3 position){ 186 | m_fixv3LogicPosition = position; 187 | } 188 | 189 | // - 获取位置 190 | // 191 | // @return 当前逻辑位置 192 | public FixVector3 getPosition(){ 193 | return m_fixv3LogicPosition; 194 | } 195 | 196 | // - 自杀 197 | // 198 | // @return none 199 | virtual public void killSelf(){ 200 | stopAllAction(); 201 | killActionManager(); 202 | 203 | if (null != m_statemachine) { 204 | m_statemachine.exitOldState(); 205 | } 206 | 207 | m_bKilled = true; 208 | 209 | checkEvent(); 210 | } 211 | 212 | //- 检查状态 213 | // 在冷却状态结束后检测一下当前状态,以便根据当前状态刷新逻辑 214 | // @return none 215 | virtual public void checkStatue() 216 | { 217 | 218 | } 219 | 220 | //- 记录最后的位置 221 | // 222 | // @return none. 223 | public void recordLastPos(){ 224 | m_fixv3LastPosition = m_fixv3LogicPosition; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /core/base/BaseObject.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7439ac13f89f47446aa4b06ea6d1cc90 3 | timeCreated: 1524553431 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/base/LiveObject.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 存在的对象基类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | using System.Collections.Generic; 11 | 12 | public class LiveObject : BaseObject 13 | { 14 | //血量 15 | private Fix64 m_fixHp = Fix64.Zero; 16 | public Fix64 hp { get { return m_fixHp; } set { m_fixHp = value; } } 17 | 18 | //初始血量 19 | private Fix64 m_fixOrignalHp = Fix64.Zero; 20 | public Fix64 orignalHp { get { return m_fixOrignalHp; } set { m_fixOrignalHp = value; } } 21 | 22 | //普通伤害 23 | private Fix64 m_fixDamage = Fix64.Zero; 24 | public Fix64 damage { get { return m_fixDamage; } set { m_fixDamage = value; } } 25 | 26 | //攻击我的列表 27 | public List m_listAttackMe = new List(); 28 | 29 | //攻击我的子弹列表(死亡后通知其失效) 30 | public List m_listAttackMeBullet = new List(); 31 | 32 | //我正在攻击的列表 33 | public List m_listAttackingList = new List(); 34 | 35 | //侦测范围 36 | private Fix64 m_fixAttackRange = Fix64.Zero; 37 | public Fix64 attackRange { get { return m_fixAttackRange; } set { m_fixAttackRange = value; } } 38 | 39 | //攻击速度 40 | private Fix64 m_fixAttackSpeed = Fix64.Zero; 41 | public Fix64 attackSpeed { get { return m_fixAttackSpeed; } set { m_fixAttackSpeed = value; } } 42 | 43 | //锁定的攻击对象 44 | private LiveObject m_lockedAttackUnit = null; 45 | public LiveObject lockedAttackUnit { get { return m_lockedAttackUnit; } set { m_lockedAttackUnit = value; } } 46 | 47 | //是否处于冷却状态 48 | private bool m_bIsCooling = false; 49 | public bool isCooling { get { return m_bIsCooling; } set { m_bIsCooling = value; } } 50 | 51 | //- 设置血量 52 | // 53 | // @param value 要设置的血量值 54 | // @return none 55 | public void setHp(Fix64 value) { 56 | m_fixHp = value; 57 | m_fixOrignalHp = value; 58 | } 59 | 60 | //- 获取血量 61 | // @return none 62 | // @author 63 | public Fix64 getHp() 64 | { 65 | return m_fixHp; 66 | } 67 | 68 | //- 添加我正在攻击的对象 69 | // 用于在死亡时通知对应对象 70 | // @param obj 要攻击的对象 71 | // @return none 72 | public void addAttackingObj(LiveObject obj) 73 | { 74 | //判断是否已经添加过 75 | if (m_listAttackingList.Contains(obj)) 76 | { 77 | return; 78 | } 79 | 80 | //插入队列 81 | m_listAttackingList.Add(obj); 82 | } 83 | 84 | //- 移除我正在攻击的对象 85 | // 对方死亡时,我要从自己正在攻击的队列中把对方移除掉 86 | // @param obj 要移除的对象 87 | // @return none 88 | public void removeAttackingObj(LiveObject obj) 89 | { 90 | m_listAttackingList.Remove(obj); 91 | } 92 | 93 | //- 添加正在攻击我的对象 94 | // 用于在死亡时通知对应对象 95 | // @param obj 正在攻击我的对象 96 | // @return none 97 | public void addAttackMeObj(LiveObject obj) 98 | { 99 | //判断是否已经添加过 100 | if (m_listAttackMe.Contains(obj)) 101 | { 102 | return; 103 | } 104 | 105 | //插入队列 106 | m_listAttackMe.Add(obj); 107 | } 108 | 109 | //- 移除正在攻击我的对象 110 | // 对方死亡时,要从对方正在攻击的队列中把我移除掉 111 | // @param obj 要移除的对象 112 | // @return none 113 | public void removeAttackMeObj(LiveObject obj) 114 | { 115 | m_listAttackMe.Remove(obj); 116 | } 117 | 118 | //- 添加正在攻击我的子弹对象 119 | // 用于在死亡时通知对应子弹对象 120 | // @param obj 正在攻击我的子弹对象 121 | // @return none 122 | public void addAttackMeBulletObj(BaseObject obj) 123 | { 124 | //判断是否已经添加过 125 | if (m_listAttackMeBullet.Contains(obj)) 126 | { 127 | return; 128 | } 129 | 130 | //插入队列 131 | m_listAttackMeBullet.Add(obj); 132 | } 133 | 134 | //- 移除正在攻击我的子弹对象 135 | // 对方死亡时, 要从正在攻击我的子弹队列中移除掉该子弹 136 | // @param obj 要移除的对象 137 | // @return none 138 | public void removeAttackMeBulletObj(BaseObject obj) 139 | { 140 | m_listAttackMeBullet.Remove(obj); 141 | } 142 | 143 | 144 | //- 发送死亡信息给相关对象 145 | // 146 | // @return none 147 | public void sendDeadInfoToRelativeObj() 148 | { 149 | //print("name . ", name) 150 | //print("#attackMeList . ", #attackMeList) 151 | //让所有攻击我的子弹失效 152 | for (int i = m_listAttackMeBullet.Count - 1; i >= 0; i--) { 153 | m_listAttackMeBullet[i].uneffect = true; 154 | removeAttackMeBulletObj(m_listAttackMeBullet[i]); 155 | } 156 | 157 | //通知我正在攻击的对象,我已经死了,从我正在攻击的对象身上把自身移除 158 | for (int i = m_listAttackingList.Count - 1; i >= 0; i--) { 159 | LiveObject obj = m_listAttackingList[i]; 160 | obj.removeAttackMeObj(this); 161 | removeAttackingObj(obj); 162 | } 163 | 164 | //通知正在攻击我的对象,我已经死了,别打了 165 | for (int i = m_listAttackMe.Count - 1; i >= 0; i--) { 166 | LiveObject obj = m_listAttackMe[i]; 167 | obj.removeAttackingObj(this); 168 | removeAttackMeObj(obj); 169 | 170 | if (obj.m_scType == "tower") 171 | { 172 | //print("current state . ", obj.getState()) 173 | if (obj.getState() != "cooling") 174 | { 175 | obj.changeState("towerstand"); 176 | } 177 | else 178 | { 179 | obj.setPrevStateName("towerstand"); 180 | } 181 | } 182 | } 183 | } 184 | 185 | //- 设置攻击力 186 | // 187 | // @param value 攻击力 188 | // @return none 189 | public void setDamageValue(Fix64 value){ 190 | m_fixDamage = value; 191 | } 192 | 193 | //- 获取攻击力 194 | // 195 | // @return none 196 | public Fix64 getDamageValue() 197 | { 198 | return m_fixDamage; 199 | } 200 | 201 | //- 受到伤害 202 | // 203 | // @param damage 被伤害的值 204 | // @return none 205 | public void beDamage(Fix64 damage, bool isSrcCrit = false) 206 | { 207 | if (false == m_bKilled) { 208 | //播放被攻击的动画 209 | if (m_scType == "tower") { 210 | playAnimation("Hurt"); 211 | 212 | delayDo((Fix64)0.5, delegate () { playAnimation("Stand"); }, "delaytostand"); 213 | } 214 | 215 | //扣血,如果扣到小于等于0则死亡 216 | m_fixHp = m_fixHp - damage; 217 | 218 | if (m_fixHp <= Fix64.Zero) { 219 | m_bKilled = true; 220 | } 221 | } 222 | } 223 | 224 | 225 | //- 自杀 226 | // 227 | // @return none 228 | public override void killSelf() 229 | { 230 | //告知所有攻击我的对象,别打了,恢复正常吧 231 | sendDeadInfoToRelativeObj(); 232 | 233 | base.killSelf(); 234 | } 235 | 236 | //- 加载属性 237 | // 238 | // @return none 239 | public virtual void loadProperties() 240 | { 241 | 242 | } 243 | 244 | //- 获取攻击范围 245 | // 246 | // @return none 247 | public Fix64 getAttackRange() 248 | { 249 | return m_fixAttackRange; 250 | } 251 | 252 | //- 跳转到对应的状态 253 | // 254 | // @param state 要跳转到的状态 255 | // @return none 256 | public void changeState(string state) 257 | { 258 | m_statemachine.changeState(state, (Fix64)0); 259 | } 260 | 261 | //- 跳转到对应的状态 262 | // 263 | // @param state 要跳转到的状态 264 | // @return none 265 | public void changeState(string state, Fix64 args) 266 | { 267 | m_statemachine.changeState(state, args); 268 | } 269 | 270 | //- 设置之前的状态的名字 271 | // 记录之前的状态,某些状态需要在执行后恢复到之前的状态,所以需要记录 272 | // @param stateName 要记录的状态名 273 | // @return none 274 | public void setPrevStateName(string stateName) 275 | { 276 | m_statemachine.setPrevStateName(stateName); 277 | } 278 | 279 | //- 获取之前的状态的名字 280 | // @return 之前的状态的名字 281 | public string getPrevStateName() 282 | { 283 | return m_statemachine.getPrevStateName(); 284 | } 285 | 286 | //- 获取当前状态 287 | // 288 | // @return 当前状态 289 | public string getState() 290 | { 291 | return m_statemachine.getState(); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /core/base/LiveObject.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0138cdc079280c7429f86b2f8010423b 3 | timeCreated: 1524553446 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/base/UnityObject.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: unity对象基类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | #if _CLIENTLOGIC_ 10 | using UnityEngine; 11 | #endif 12 | 13 | using System.Collections; 14 | 15 | public class UnityObject 16 | { 17 | public string m_scBundle = ""; 18 | public string m_scAsset = ""; 19 | public string m_scType = ""; 20 | 21 | //是否被杀掉了 22 | public bool m_bKilled = false; 23 | #if _CLIENTLOGIC_ 24 | public GameObject m_gameObject; 25 | #endif 26 | //最后的位置 27 | public FixVector3 m_fixv3LastPosition = new FixVector3(Fix64.Zero, Fix64.Zero, Fix64.Zero); 28 | 29 | //逻辑位置 30 | public FixVector3 m_fixv3LogicPosition = new FixVector3(Fix64.Zero, Fix64.Zero, Fix64.Zero); 31 | 32 | //旋转值 33 | FixVector3 m_fixv3LogicRotation; 34 | 35 | //缩放值 36 | FixVector3 m_fixv3LogicScale; 37 | 38 | public void createFromPrefab(string path, UnityObject script) { 39 | #if _CLIENTLOGIC_ 40 | prefab.create(path, script); 41 | #endif 42 | } 43 | 44 | 45 | public void updateRenderPosition(float interpolation) { 46 | #if _CLIENTLOGIC_ 47 | if (m_bKilled) 48 | { 49 | return; 50 | } 51 | 52 | //只有会移动的对象才需要采用插值算法补间动画,不会移动的对象直接设置位置即可 53 | if ((m_scType == "soldier" || m_scType == "bullet") && interpolation != 0) 54 | { 55 | m_gameObject.transform.localPosition = Vector3.Lerp(m_fixv3LastPosition.ToVector3(), m_fixv3LogicPosition.ToVector3(), interpolation); 56 | } 57 | else 58 | { 59 | m_gameObject.transform.localPosition = m_fixv3LogicPosition.ToVector3(); 60 | } 61 | #endif 62 | } 63 | 64 | //- 播放动画 65 | // 66 | // @param animationName 动画名 67 | // @return; none 68 | public void playAnimation(string animationName) { 69 | 70 | } 71 | 72 | //- 排队播放动画 73 | // 74 | // @param animationName 动画名 75 | // @return; none 76 | public void playAnimationQueued(string animationName) { 77 | #if _CLIENTLOGIC_ 78 | 79 | #endif 80 | } 81 | 82 | //- 停止动画 83 | // 84 | // @return; none 85 | public void stopAnimation() { 86 | #if _CLIENTLOGIC_ 87 | Animation animation = m_gameObject.transform.GetComponent(); 88 | if (null != animation) 89 | { 90 | animation.Stop(); 91 | } 92 | #endif 93 | } 94 | 95 | //- 设置缩放值 96 | // 97 | // @param value 要设置的缩放值 98 | // @return; none 99 | public void setScale(FixVector3 value) 100 | { 101 | m_fixv3LogicScale = value; 102 | 103 | #if _CLIENTLOGIC_ 104 | m_gameObject.transform.localScale = value.ToVector3(); 105 | #endif 106 | } 107 | 108 | //- 获取缩放值 109 | // 110 | // @return; 缩放值 111 | public FixVector3 getScale() 112 | { 113 | return m_fixv3LogicScale; 114 | } 115 | 116 | //- 设置旋转值 117 | // 118 | // @param value 要设置的旋转值 119 | // @return; none 120 | public void setRotation(FixVector3 value) 121 | { 122 | m_fixv3LogicRotation = value; 123 | #if _CLIENTLOGIC_ 124 | m_gameObject.transform.localEulerAngles = value.ToVector3(); 125 | setVisible(true); 126 | #endif 127 | } 128 | 129 | //- 获取旋转值 130 | // 131 | // @return; 旋转值 132 | public FixVector3 getRotation() 133 | { 134 | return m_fixv3LogicRotation; 135 | } 136 | 137 | //- 设置是否可见 138 | // 139 | // @param value 是否可见 140 | // @return; none 141 | public void setVisible(bool value) 142 | { 143 | #if _CLIENTLOGIC_ 144 | m_gameObject.SetActive(value); 145 | #endif 146 | } 147 | 148 | //- 删除gameobject 149 | // 150 | // @return; none 151 | public void destroyGameObject() 152 | { 153 | #if _CLIENTLOGIC_ 154 | GameObject.Destroy(m_gameObject); 155 | m_gameObject.transform.localPosition = new Vector3(10000, 10000, 0); 156 | #endif 157 | } 158 | 159 | //- 设置GameObject的名字 160 | // 161 | // @param name 名字 162 | // @return; none 163 | public void setGameObjectName(string name) 164 | { 165 | #if _CLIENTLOGIC_ 166 | m_gameObject.name = name; 167 | #endif 168 | } 169 | 170 | //- 获取GameObject的名字 171 | // 172 | // @return; GameObject的名字 173 | public string getGameObjectName() 174 | { 175 | #if _CLIENTLOGIC_ 176 | return m_gameObject.name; 177 | #else 178 | return ""; 179 | #endif 180 | } 181 | 182 | //- 设置位置 183 | // 184 | // @param position 要设置到的位置 185 | // @return; none 186 | public void setGameObjectPosition(FixVector3 position) 187 | { 188 | #if _CLIENTLOGIC_ 189 | m_gameObject.transform.localPosition = position.ToVector3(); 190 | #endif 191 | } 192 | 193 | //- 获取位置 194 | // 195 | // @return; 当前逻辑位置 196 | //public FixVector3 getPosition() { 197 | // if (!GameData.g_client) { return new FixVector3(Fix64.Zero, Fix64.Zero, Fix64.Zero);} 198 | 199 | // return gameObject.transform.localPosition; 200 | // } 201 | 202 | //- 设置颜色 203 | // 204 | // @param r 红 205 | // @param g 绿 206 | // @param b 蓝 207 | // @return; none 208 | public void setColor(float r, float g, float b) 209 | { 210 | #if _CLIENTLOGIC_ 211 | m_gameObject.GetComponent().color = new Color(r, g, b, 1); 212 | #endif 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /core/base/UnityObject.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5d983dd89480f81419d29fbfec1ca5f9 3 | timeCreated: 1524555566 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/bullet.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 481fae289c615034d921d702355a167b 3 | folderAsset: yes 4 | timeCreated: 1524553457 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /core/bullet/BaseBullet.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 子弹基类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | using System.Collections.Generic; 11 | 12 | public class BaseBullet : BaseObject { 13 | LiveObject m_src = null; 14 | protected LiveObject m_dest = null; 15 | protected FixVector3 m_fixv3SrcPosition = new FixVector3(); 16 | protected FixVector3 m_fixv3DestPosition = new FixVector3(); 17 | protected Fix64 m_fixDamage = Fix64.Zero; 18 | 19 | //- 每帧循环 20 | // 21 | // @return none 22 | virtual public void updateLogic() { 23 | checkEvent(); 24 | } 25 | 26 | //- 初始化数据 27 | // 28 | // @param src 发射源 29 | // @param dest 射击目标 30 | // @param poOri 发射的起始位置 31 | // @param poDst 发射的目标位置 32 | // @return none. 33 | virtual public void initData(LiveObject src1, LiveObject dest1, FixVector3 poOri, FixVector3 poDst) { 34 | m_scType = "bullet"; 35 | 36 | loadProperties(); 37 | 38 | m_src = src1; 39 | m_dest = dest1; 40 | m_fixv3SrcPosition = poOri; 41 | m_fixv3DestPosition = poDst; 42 | 43 | m_fixDamage = m_src.getDamageValue(); 44 | 45 | m_dest.addAttackMeBulletObj(this); 46 | } 47 | 48 | //- 射击 49 | // 50 | // @return none. 51 | virtual public void shoot() { 52 | 53 | 54 | } 55 | 56 | //- 攻击目标对象 57 | // 58 | // @return none. 59 | virtual public void doShootDest() { 60 | //目标被扣血 61 | if (false == m_bUneffect) { 62 | removeFromDestBulletList(); 63 | 64 | m_dest.beDamage(m_fixDamage); 65 | } 66 | 67 | m_bKilled = true; 68 | } 69 | 70 | //- 从攻击者的子弹列表中移除自身 71 | // 避免被攻击者已经死了子弹还在攻击的问题 72 | // @return none 73 | protected void removeFromDestBulletList() { 74 | List list = m_dest.m_listAttackMeBullet; 75 | list.Remove(this); 76 | } 77 | 78 | //- 加载属性 79 | // 80 | // @param id 类型id 81 | // @return none 82 | virtual public void loadProperties() { 83 | 84 | 85 | } 86 | 87 | //- 根据名字加载预制体 88 | // 89 | // @param name 子弹的名字 90 | // @return none 91 | virtual public void createBody(string name) { 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /core/bullet/BaseBullet.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0114a3586f4ce4644bcf9c6c49270421 3 | timeCreated: 1524553468 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/bullet/BulletFactory.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 子弹工厂类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class BulletFactory { 12 | 13 | string m_scBulletName = ""; 14 | 15 | //- 创建子弹 16 | // 17 | // @param id 子弹id 18 | // @param dest 由谁发出来的子弹 19 | // @param src 由谁发出来的子弹 20 | // @param poShootStartPosition 子弹生成的位置 21 | // @param poShootEndPosition 子弹要到达的位置 22 | // @return none 23 | public void createBullet(LiveObject src, LiveObject dest, FixVector3 poShootStartPosition, FixVector3 poShootEndPosition) { 24 | BaseBullet bullet = null; 25 | 26 | //直射子弹 27 | bullet = new DirectionShootBullet(); 28 | 29 | //根据名字加载资源 30 | bullet.createBody(m_scBulletName); 31 | 32 | bullet.initData(src, dest, poShootStartPosition, poShootEndPosition); 33 | bullet.shoot(); 34 | 35 | if (null != bullet) { 36 | //刷新显示位置 37 | bullet.updateRenderPosition(0); 38 | 39 | //立即记录最后的位置,否则通过vector3.lerp来进行移动动画时会出现画面抖动的bug 40 | bullet.recordLastPos(); 41 | 42 | //加入子弹列表 43 | GameData.g_listBullet.Add(bullet); 44 | } 45 | } 46 | 47 | //- 移除子弹 48 | // 49 | // @param bullet 要移除的子弹对象 50 | // @return none 51 | void removeBullet(BaseBullet bullet){ 52 | GameData.g_listBullet.Remove(bullet); 53 | } 54 | 55 | //- 加载属性 56 | // 57 | // @return none 58 | void loadProperties(){ 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/bullet/BulletFactory.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 823b98ef7565c474faa1620f73743f2a 3 | timeCreated: 1524553478 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/bullet/DirectionShootBullet.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 直射子弹 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class DirectionShootBullet : BaseBullet 12 | { 13 | Fix64 m_fixMoveTime = Fix64.Zero; 14 | Fix64 m_fixSpeed = Fix64.Zero; 15 | 16 | //- 每帧循环 17 | // 18 | // @return none 19 | public override void updateLogic() 20 | { 21 | //调用父类Update 22 | base.updateLogic(); 23 | } 24 | 25 | //- 初始化数据 26 | // 27 | // @param src 发射源 28 | // @param dest 射击目标 29 | // @param poOri 发射的起始位置 30 | // @param poDst 发射的目标位置 31 | // @return none. 32 | public override void initData(LiveObject src, LiveObject dest, FixVector3 poOri, FixVector3 poDst) 33 | { 34 | base.initData(src, dest, poOri, poDst); 35 | 36 | Fix64 distance = FixVector3.Distance(poOri, poDst); 37 | 38 | m_fixMoveTime = distance / m_fixSpeed; 39 | } 40 | 41 | //- 射击 42 | // 43 | // @return none. 44 | public override void shoot() 45 | { 46 | m_fixv3LogicPosition = m_fixv3SrcPosition; 47 | 48 | moveTo(m_fixv3SrcPosition, m_fixv3DestPosition, m_fixMoveTime, delegate () 49 | { 50 | doShootDest(); 51 | }); 52 | } 53 | 54 | //- 根据名字加载预制体 55 | // 56 | // @param name 子弹的名字 57 | // @return none 58 | public override void createBody(string nameValue) 59 | { 60 | //加载子弹主体 61 | createFromPrefab("Prefabs/Bullet", this); 62 | 63 | //名字 64 | m_scName = nameValue; 65 | } 66 | 67 | //- 加载属性 68 | // 69 | // @return none 70 | public override void loadProperties() 71 | { 72 | m_fixSpeed = (Fix64)10; 73 | } 74 | } -------------------------------------------------------------------------------- /core/bullet/DirectionShootBullet.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a0ae7253437744147b29842e7ca5286d 3 | timeCreated: 1524553498 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/soldier.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fe6df36ffedfe2a4b9b299f39cc0562d 3 | folderAsset: yes 4 | timeCreated: 1524553692 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /core/soldier/BaseSoldier.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 士兵类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class BaseSoldier : LiveObject 12 | { 13 | public BaseSoldier() 14 | { 15 | init(); 16 | } 17 | 18 | void init() 19 | { 20 | //设置类型 21 | m_scType = "soldier"; 22 | 23 | //状态机对象 24 | m_statemachine = new StateMachine(); 25 | 26 | //设置起作用的单元主体 27 | m_statemachine.setUnit(this); 28 | } 29 | 30 | //- 移动 31 | // 32 | // @return none 33 | public void move() { 34 | //跳转到移动的状态 35 | changeState("soldiermove"); 36 | } 37 | 38 | //- 每帧循环 39 | // 40 | // @return none 41 | virtual public void updateLogic(){ 42 | m_statemachine.updateLogic(); 43 | 44 | checkIsDead(); 45 | checkEvent(); 46 | } 47 | 48 | //- 检查状态 49 | // 在冷却状态结束后检测一下当前状态,以便根据当前状态刷新逻辑 50 | // @return none 51 | public override void checkStatue(){ 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/soldier/BaseSoldier.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bcff92ea26384834fa5c058f34bd3f6c 3 | timeCreated: 1524553706 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/soldier/Grizzly.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 豺狼人 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | 10 | using System.Collections; 11 | 12 | public class Grizzly : BaseSoldier 13 | { 14 | 15 | public Grizzly() 16 | { 17 | loadProperties(); 18 | 19 | base.createFromPrefab("Prefabs/Soldier", this); 20 | 21 | //设置类型 22 | m_scName = "grizzly"; 23 | } 24 | 25 | //- 每帧循环 26 | // 27 | // @return none 28 | public override void updateLogic() 29 | { 30 | //调用父类Update 31 | base.updateLogic(); 32 | } 33 | 34 | //- 加载属性 35 | // 36 | // @return none 37 | public override void loadProperties() 38 | { 39 | setHp((Fix64)200); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/soldier/Grizzly.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9ff53cb7e2c863d49a63547d9300ce0b 3 | timeCreated: 1524553728 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/soldier/SoldierFactory.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 士兵工厂类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class SoldierFactory 12 | { 13 | 14 | //- 创建士兵 15 | // 16 | // @return 创建出的士兵. 17 | public BaseSoldier createSoldier() { 18 | BaseSoldier soldier; 19 | 20 | soldier = new Grizzly(); 21 | 22 | GameData.g_listSoldier.Add(soldier); 23 | 24 | //立即记录最后的位置,否则通过vector3.lerp来进行移动动画时会出现画面抖动的bug 25 | soldier.recordLastPos(); 26 | 27 | return soldier; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/soldier/SoldierFactory.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b68bf6bfdb158d4e8b8355b12eec429 3 | timeCreated: 1524553737 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/state.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4c9cbd211de875f4aa8eb714fd1dc216 3 | folderAsset: yes 4 | timeCreated: 1524553753 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /core/state/BaseState.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 状态机对象基类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class BaseState 12 | { 13 | //所挂载的主体单元 14 | protected LiveObject m_unit = null; 15 | 16 | //之前的状态名 17 | protected string m_scPrevStateName = ""; 18 | 19 | //当前状态名 20 | protected string m_scName = ""; 21 | 22 | //- 创建时进入的初始化函数 23 | // 24 | // @param args 附加的创建信息 25 | // @return none 26 | virtual public void onInit(LiveObject args) { 27 | 28 | } 29 | 30 | //- 进入该状态时调用的函数 31 | // 32 | // @param args 附加的调用信息 33 | // @return none 34 | virtual public void onEnter(Fix64 args) 35 | { 36 | 37 | } 38 | 39 | //- 退出该状态时调用的函数 40 | // 41 | // @return none 42 | virtual public void onExit() 43 | { 44 | 45 | } 46 | 47 | //- 处于该状态时每帧调用的函数 48 | // 49 | // @return none 50 | virtual public void updateLogic() 51 | { 52 | 53 | } 54 | 55 | //- 设置之前的状态的名字 56 | // 记录之前的状态,某些状态需要在执行后恢复到之前的状态,所以需要记录 57 | // @param stateName 要记录的状态名 58 | // @return none 59 | public void setPrevStateName(string stateName) 60 | { 61 | m_scPrevStateName = stateName; 62 | } 63 | 64 | //- 获取之前的状态的名字 65 | // @return 之前的状态的名字 66 | public string getPrevStateName() 67 | { 68 | return m_scPrevStateName; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /core/state/BaseState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e345f24c6dfbf6a4db7dfeff20aa65de 3 | timeCreated: 1524553760 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/state/CoolingState.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 冷却状态 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class CoolingState : BaseState 12 | { 13 | public CoolingState() 14 | { 15 | init(); 16 | } 17 | 18 | //- 初始化函数. 19 | // Some description, can be over several lines. 20 | // @return value description. 21 | void init() { 22 | m_scName = "cooling"; 23 | } 24 | 25 | //- 创建时进入的初始化函数 26 | // 27 | // @param args 附加的创建信息 28 | // @return none 29 | public override void onInit(LiveObject args) { 30 | m_unit = args; 31 | } 32 | 33 | //- 进入该状态时调用的函数 34 | // 35 | // @param args 附加的调用信息 36 | // @return none 37 | public override void onEnter(Fix64 args) { 38 | m_unit.isCooling = true; 39 | 40 | //冷却时间 41 | Fix64 cdtime = args; 42 | 43 | m_unit.delayDo(cdtime, delegate () { 44 | if (null != m_scPrevStateName) 45 | { 46 | m_unit.checkStatue(); 47 | m_unit.changeState(m_scPrevStateName); 48 | } 49 | }, "changePrevState"); 50 | } 51 | 52 | 53 | //- 退出该状态时调用的函数 54 | // 55 | // @return none 56 | public override void onExit() 57 | { 58 | m_unit.isCooling = false; 59 | m_unit.stopAction("changePrevState"); 60 | } 61 | 62 | //- 处于该状态时每帧调用的函数 63 | // 64 | // @return none 65 | public override void updateLogic() 66 | { 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /core/state/CoolingState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3d04307679dda1246b49c7ad7e5c7b07 3 | timeCreated: 1524553768 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/state/NormalState.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 普通状态 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class NormalState : BaseState 12 | { 13 | public NormalState() 14 | { 15 | init(); 16 | } 17 | 18 | //- 初始化函数. 19 | // Some description, can be over several lines. 20 | // @return value description. 21 | void init() 22 | { 23 | m_scName = "normal"; 24 | } 25 | 26 | //- 创建时进入的初始化函数 27 | // 28 | // @param args 附加的创建信息 29 | // @return none 30 | public override void onInit(LiveObject args) 31 | { 32 | m_unit = args; 33 | } 34 | 35 | 36 | 37 | //- 进入该状态时调用的函数 38 | // 39 | // @param args 附加的调用信息 40 | // @return none 41 | public override void onEnter(Fix64 args) 42 | { 43 | 44 | } 45 | 46 | 47 | //- 退出该状态时调用的函数tgfdsdfgdfsgfgggggggg 48 | // 49 | // @return none 50 | public override void onExit() 51 | { 52 | 53 | } 54 | 55 | //- 处于该状态时每帧调用的函数 56 | // 57 | // @return none 58 | public override void updateLogic() 59 | { 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /core/state/NormalState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e1bea6fe7147cdf4eb09c87ceba526b0 3 | timeCreated: 1524553785 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/state/StateMachine.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 状态机类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class StateMachine { 12 | BaseState m_currentState = null; 13 | string m_scCurrentStateName = ""; 14 | LiveObject m_unit = null; 15 | 16 | // - 每帧循环 17 | // 由MainLogic的Update来进行调用, 自己不会调用 18 | // @return value description. 19 | public void updateLogic() 20 | { 21 | if (null != m_currentState) { 22 | m_currentState.updateLogic(); 23 | } 24 | } 25 | 26 | //- 更改状态 27 | // 28 | // @param state 要更改到的状态 29 | // @return none 30 | public void changeState(string state, Fix64 args) { 31 | //检查是否存在之前的状态,有则先退出之前的状态,做好善后工作 32 | exitOldState(); 33 | 34 | m_currentState = null; 35 | 36 | //根据不同的状态参数创建对应的状态 37 | if (state == "towerattack") { 38 | m_currentState = new TowerAttackState(); 39 | } 40 | else if (state == "towerstand") 41 | { 42 | m_currentState = new TowerStandState(); 43 | } 44 | else if (state == "normal") 45 | { 46 | m_currentState = new NormalState(); 47 | } 48 | else if (state == "cooling") 49 | { 50 | m_currentState = new CoolingState(); 51 | } 52 | 53 | //为新创建的状态做好准备工作 54 | m_currentState.onInit(m_unit); 55 | 56 | //设置之前的状态名 57 | m_currentState.setPrevStateName(m_scCurrentStateName); 58 | 59 | //记录当前的状态名 60 | m_scCurrentStateName = state; 61 | 62 | //直接进入该状态 63 | m_currentState.onEnter(args); 64 | } 65 | 66 | //- 设置之前的状态的名字 67 | // 记录之前的状态,某些状态需要在执行后恢复到之前的状态,所以需要记录 68 | // @param stateName 要记录的状态名 69 | // @return none 70 | public void setPrevStateName(string stateName){ 71 | m_currentState.setPrevStateName(stateName); 72 | } 73 | 74 | //- 获取之前的状态的名字 75 | // @return 之前的状态的名字 76 | public string getPrevStateName() { 77 | return m_currentState.getPrevStateName(); 78 | } 79 | 80 | //- 获取当前状态 81 | // 82 | // @return 当前状态 83 | public string getState(){ 84 | return m_scCurrentStateName; 85 | } 86 | 87 | //- 退出之前的状态 88 | // 89 | // @return none 90 | public void exitOldState(){ 91 | if (null != m_currentState) { 92 | m_currentState.onExit(); 93 | } 94 | } 95 | 96 | //- 设置起作用的单元主体 97 | // 98 | // @param unit 作用于的单元主体 99 | // @return none 100 | public void setUnit(LiveObject value){ 101 | m_unit = value; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /core/state/StateMachine.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df46d265b273d984ab6f3642e4ef7388 3 | timeCreated: 1524553871 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/state/TowerAttackState.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 塔攻击状态 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class TowerAttackState : BaseState 12 | { 13 | public TowerAttackState() 14 | { 15 | init(); 16 | } 17 | 18 | //- 初始化函数. 19 | // Some description, can be over several lines. 20 | // @return value description. 21 | void init() 22 | { 23 | m_scName = "towerattack"; 24 | } 25 | 26 | //- 创建时进入的初始化函数 27 | // 28 | // @param args 附加的创建信息 29 | // @return none 30 | public override void onInit(LiveObject args) 31 | { 32 | m_unit = args; 33 | } 34 | 35 | //- 进入该状态时调用的函数 36 | // 37 | // @param args 附加的调用信息 38 | // @return none 39 | public override void onEnter(Fix64 args) 40 | { 41 | BaseSoldier soldier = (BaseSoldier)m_unit.lockedAttackUnit; 42 | 43 | GameData.g_bulletManager.createBullet(m_unit, soldier, m_unit.m_fixv3LogicPosition, soldier.m_fixv3LogicPosition); 44 | m_unit.changeState("cooling", m_unit.attackSpeed); 45 | } 46 | 47 | 48 | //- 退出该状态时调用的函数tgfdsdfgdfsgfgggggggg 49 | // 50 | // @return none 51 | public override void onExit() 52 | { 53 | 54 | } 55 | 56 | //- 处于该状态时每帧调用的函数 57 | // 58 | // @return none 59 | public override void updateLogic() 60 | { 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/state/TowerAttackState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c7e68e151265355448107e14f8635d3a 3 | timeCreated: 1524553881 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/state/TowerStandState.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 塔待机状态 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class TowerStandState : BaseState 12 | { 13 | public static Fix64 s_fixTestCount = (Fix64)0; 14 | 15 | public static string s_scTestContent = ""; 16 | public TowerStandState() 17 | { 18 | init(); 19 | } 20 | 21 | //- 初始化函数. 22 | // Some description, can be over several lines. 23 | // @return value description. 24 | void init() 25 | { 26 | m_scName = "towerstand"; 27 | } 28 | 29 | //- 创建时进入的初始化函数 30 | // 31 | // @param args 附加的创建信息 32 | // @return none 33 | public override void onInit(LiveObject args) 34 | { 35 | m_unit = args; 36 | } 37 | 38 | 39 | 40 | //- 进入该状态时调用的函数 41 | // 42 | // @param args 附加的调用信息 43 | // @return none 44 | public override void onEnter(Fix64 args) 45 | { 46 | //播放待机动画 47 | m_unit.playAnimation("Stand"); 48 | } 49 | 50 | 51 | //- 退出该状态时调用的函数tgfdsdfgdfsgfgggggggg 52 | // 53 | // @return none 54 | public override void onExit() 55 | { 56 | 57 | } 58 | 59 | //- 处于该状态时每帧调用的函数 60 | // 61 | // @return none 62 | public override void updateLogic() 63 | { 64 | //UnityTools.Log("towerstand"); 65 | for (int i = 0; i < GameData.g_listSoldier.Count; i++) 66 | { 67 | var soldier = GameData.g_listSoldier[i]; 68 | 69 | Fix64 distance = FixVector3.Distance(m_unit.m_fixv3LogicPosition, soldier.m_fixv3LogicPosition); 70 | s_scTestContent += distance.ToString() + ","; 71 | 72 | //如果进入攻击范围并且大于禁止攻击的范围 73 | if (distance <= (Fix64)m_unit.attackRange) 74 | { 75 | s_fixTestCount += distance; 76 | m_unit.lockedAttackUnit = soldier; 77 | 78 | m_unit.addAttackingObj(soldier); 79 | soldier.addAttackMeObj(m_unit); 80 | 81 | m_unit.changeState("towerattack"); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /core/state/TowerStandState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 44e1936a2f89137429c2eace2b191054 3 | timeCreated: 1524553890 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/tower.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a2641d436796f37408b03f5d02c5a685 3 | folderAsset: yes 4 | timeCreated: 1524553898 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /core/tower/BaseTower.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 塔基类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class BaseTower : LiveObject 12 | { 13 | public BaseTower() 14 | { 15 | init(); 16 | } 17 | 18 | void init() 19 | { 20 | //设置类型为塔 21 | m_scType = "tower"; 22 | 23 | //状态机对象 24 | m_statemachine = new StateMachine(); 25 | 26 | //设置起作用的单元主体 27 | m_statemachine.setUnit(this); 28 | } 29 | 30 | //- 每帧循环 31 | // 32 | // @return none 33 | virtual public void updateLogic() { 34 | //状态机 35 | m_statemachine.updateLogic(); 36 | 37 | //检测是否已经死亡 38 | checkIsDead(); 39 | 40 | //检测事件 41 | checkEvent(); 42 | } 43 | 44 | //- 设置位置 45 | // 46 | // @param position 要设置到的位置 47 | // @return none 48 | public override void setPosition(FixVector3 position) 49 | { 50 | m_fixv3LogicPosition = position; 51 | } 52 | 53 | //- 检测敌兵是否走出攻击范围 54 | // Some description, can be over several lines. 55 | // @return value description. 56 | void checkSoldierOutRange() 57 | { 58 | if (null != lockedAttackUnit) 59 | { 60 | Fix64 distance = FixVector3.Distance(m_fixv3LogicPosition, lockedAttackUnit.m_fixv3LogicPosition); 61 | 62 | //如果走出攻击范围,则让塔恢复到待机状态 63 | if (distance > attackRange) { 64 | setPrevStateName("towerstand"); 65 | } 66 | } 67 | } 68 | 69 | //- 检查状态 70 | // 在冷却状态结束后检测一下当前状态,以便根据当前状态刷新逻辑 71 | // @return none 72 | public override void checkStatue() 73 | { 74 | checkSoldierOutRange(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /core/tower/BaseTower.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c9a2ef983308bb498e6f0610048810b 3 | timeCreated: 1524553917 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/tower/MagicStand.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 魔法塔 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class MagicStand : BaseTower 12 | { 13 | public MagicStand() 14 | { 15 | init(); 16 | } 17 | 18 | //- 初始化 19 | // 20 | // @param self 21 | // @return none 22 | void init() 23 | { 24 | loadProperties(); 25 | 26 | //每个塔加载的资源不同,所以单独处理 27 | createFromPrefab("Prefabs/Tower", this); 28 | 29 | //调用父类的构造函数 30 | //self[BASETOWER]:init(self) 31 | 32 | //设置名字为魔法塔 33 | m_scName = "magicstand"; 34 | } 35 | 36 | //- 每帧循环 37 | // 38 | // @return none 39 | public override void updateLogic() 40 | { 41 | //调用父类Update 42 | base.updateLogic(); 43 | } 44 | 45 | //- 加载属性 46 | // 47 | // @return none 48 | public override void loadProperties() 49 | { 50 | setDamageValue((Fix64)50); 51 | attackRange = (Fix64)6 + GameData.g_srand.Range(1, 3); 52 | attackSpeed = (Fix64)1; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/tower/MagicStand.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 837f11216f48bf84d8b2b25c2f270b72 3 | timeCreated: 1524553940 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /core/tower/TowerFactory.cs: -------------------------------------------------------------------------------- 1 | // 2 | // @brief: 塔工厂类 3 | // @version: 1.0.0 4 | // @author helin 5 | // @date: 8/20/2018 6 | // 7 | // 8 | // 9 | using System.Collections; 10 | 11 | public class TowerFactory 12 | { 13 | 14 | //- 创建塔 15 | // 16 | // @param name 名字 17 | // @param pos 在地图中的位置(注意是地图块的位置, 不是坐标) 18 | // @return 创建出的士兵. 19 | public BaseTower createTower() { 20 | BaseTower tower = new MagicStand(); 21 | 22 | tower.changeState("towerstand"); 23 | 24 | GameData.g_listTower.Add(tower); 25 | 26 | return tower; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/tower/TowerFactory.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9e38c9dfe2b0924eb2f9a1891012b6e 3 | timeCreated: 1524553987 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | --------------------------------------------------------------------------------